package wmo

import (
	"context"
	"go.mongodb.org/mongo-driver/bson/bsontype"
	"go.mongodb.org/mongo-driver/mongo"
	ct "gogs.mikescher.com/BlackForestBytes/goext/cursortoken"
	"gogs.mikescher.com/BlackForestBytes/goext/langext"
	"reflect"
)

type EntityID interface {
	MarshalBSONValue() (bsontype.Type, []byte, error)
	String() string
}

type Decodable interface {
	Decode(v any) error
}

type Cursorable interface {
	Decode(v any) error
	Err() error
	Close(ctx context.Context) error
	All(ctx context.Context, results any) error
	RemainingBatchLength() int
	Next(ctx context.Context) bool
}

type fullTypeRef struct {
	IsPointer      bool
	Kind           reflect.Kind
	RealType       reflect.Type
	Type           reflect.Type
	UnderlyingType reflect.Type
	Name           string
	Index          []int
}

type Coll[TData any] struct {
	coll                *mongo.Collection                                        // internal mongo collection, access via Collection()
	dataTypeMap         map[string]fullTypeRef                                   // list of TData fields (only if TData is not an interface)
	implDataTypeMap     map[reflect.Type]map[string]fullTypeRef                  // dynamic list of fields of TData implementations (only if TData is an interface)
	customDecoder       *func(ctx context.Context, dec Decodable) (TData, error) // custom decoding function (useful if TData is an interface)
	isInterfaceDataType bool                                                     // true if TData is an interface (not a struct)
}

func (c *Coll[TData]) Collection() *mongo.Collection {
	return c.coll
}

func (c *Coll[TData]) Name() string {
	return c.coll.Name()
}

func (c *Coll[TData]) WithDecodeFunc(cdf func(ctx context.Context, dec Decodable) (TData, error), example TData) *Coll[TData] {

	c.EnsureInitializedReflection(example)

	c.customDecoder = langext.Ptr(cdf)
	return c
}

func (c *Coll[TData]) Indexes() mongo.IndexView {
	return c.coll.Indexes()
}

func (c *Coll[TData]) Drop(ctx context.Context) error {
	return c.coll.Drop(ctx)
}

func (c *Coll[TData]) createToken(fieldPrimary string, dirPrimary ct.SortDirection, fieldSecondary *string, dirSecondary *ct.SortDirection, lastEntity TData, pageSize *int) (ct.CursorToken, error) {

	valuePrimary, err := c.getFieldValueAsTokenString(lastEntity, fieldPrimary)
	if err != nil {
		return ct.CursorToken{}, err
	}

	valueSeconary := ""
	if fieldSecondary != nil && dirSecondary != nil {
		valueSeconary, err = c.getFieldValueAsTokenString(lastEntity, *fieldSecondary)
		if err != nil {
			return ct.CursorToken{}, err
		}
	}

	return ct.CursorToken{
		Mode:           ct.CTMNormal,
		ValuePrimary:   valuePrimary,
		ValueSecondary: valueSeconary,
		Direction:      dirPrimary,
		PageSize:       langext.Coalesce(pageSize, 0),
		Extra:          ct.Extra{},
	}, nil
}
