// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package index

import (
	"fmt"

	"github.com/hashicorp/consul/internal/resource"
	"github.com/hashicorp/consul/internal/storage"
	"github.com/hashicorp/consul/proto-public/pbresource"
)

type IndexQueryOptions struct {
	Prefix bool
}

func IndexFromID(id *pbresource.ID, includeUid bool) []byte {
	var b Builder
	b.Raw(IndexFromType(id.Type))
	b.Raw(IndexFromTenancy(id.Tenancy))
	b.String(id.Name)
	if includeUid {
		b.String(id.Uid)
	}
	return b.Bytes()
}

func IndexFromRefOrID(ref resource.ReferenceOrID) []byte {
	var b Builder
	b.Raw(IndexFromType(ref.GetType()))
	b.Raw(IndexFromTenancy(ref.GetTenancy()))
	b.String(ref.GetName())
	return b.Bytes()
}

func PrefixIndexFromRefOrID(ref resource.ReferenceOrID) []byte {
	var b Builder
	b.Raw(IndexFromType(ref.GetType()))

	raw, done := prefixIndexFromTenancy(ref.GetTenancy())
	b.Raw(raw)

	if done {
		return b.Bytes()
	}

	b.Raw([]byte(ref.GetName()))
	return b.Bytes()
}

func prefixIndexFromTenancy(t *pbresource.Tenancy) ([]byte, bool) {
	var b Builder
	partition := t.GetPartition()
	if partition == "" || partition == storage.Wildcard {
		return b.Bytes(), true
	}

	b.String(partition)

	namespace := t.GetNamespace()

	if namespace == "" || namespace == storage.Wildcard {
		return b.Bytes(), true
	}

	b.String(namespace)

	return b.Bytes(), false
}

func IndexFromType(t *pbresource.Type) []byte {
	var b Builder
	b.String(t.Group)
	b.String(t.Kind)
	return b.Bytes()
}

func IndexFromTenancy(t *pbresource.Tenancy) []byte {
	var b Builder
	b.String(t.GetPartition())
	b.String(t.GetNamespace())
	return b.Bytes()
}

var ReferenceOrIDFromArgs = SingleValueFromArgs[resource.ReferenceOrID](func(r resource.ReferenceOrID) ([]byte, error) {
	return IndexFromRefOrID(r), nil
})

var PrefixReferenceOrIDFromArgs = SingleValueFromArgs[resource.ReferenceOrID](func(r resource.ReferenceOrID) ([]byte, error) {
	return PrefixIndexFromRefOrID(r), nil
})

var MaybePrefixReferenceOrIDFromArgs = SingleValueFromOneOrTwoArgs[resource.ReferenceOrID, IndexQueryOptions](func(r resource.ReferenceOrID, opts IndexQueryOptions) ([]byte, error) {
	if opts.Prefix {
		return PrefixIndexFromRefOrID(r), nil
	} else {
		return IndexFromRefOrID(r), nil
	}
})

func SingleValueFromArgs[T any](indexer func(value T) ([]byte, error)) func(args ...any) ([]byte, error) {
	return func(args ...any) ([]byte, error) {
		var zero T
		if l := len(args); l != 1 {
			return nil, fmt.Errorf("expected 1 arg, got: %d", l)
		}

		value, ok := args[0].(T)
		if !ok {
			return nil, fmt.Errorf("expected %T, got: %T", zero, args[0])
		}

		return indexer(value)
	}
}

func SingleValueFromOneOrTwoArgs[T1 any, T2 any](indexer func(value T1, optional T2) ([]byte, error)) func(args ...any) ([]byte, error) {
	return func(args ...any) ([]byte, error) {
		var value T1
		var optional T2

		l := len(args)
		switch l {
		case 2:
			val, ok := args[1].(T2)
			if !ok {
				return nil, fmt.Errorf("expected second argument type of %T, got: %T", optional, args[1])
			}
			optional = val
			fallthrough
		case 1:
			val, ok := args[0].(T1)
			if !ok {
				return nil, fmt.Errorf("expected first argument type of %T, got: %T", value, args[0])
			}
			value = val
		default:
			return nil, fmt.Errorf("expected 1 or 2 args, got: %d", l)
		}

		return indexer(value, optional)
	}
}
