package wmo

import (
	"context"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo"
	"gogs.mikescher.com/BlackForestBytes/goext/langext"
	"gogs.mikescher.com/BlackForestBytes/goext/rfctime"
	"gogs.mikescher.com/BlackForestBytes/goext/timeext"
	"gogs.mikescher.com/BlackForestBytes/goext/tst"
	"reflect"
	"testing"
	"time"
)

func TestReflectionGetFieldType(t *testing.T) {

	type IDType string

	type TestData struct {
		ID    IDType    `bson:"_id"`
		CDate time.Time `bson:"cdate"`
		Sub   struct {
			A string `bson:"a"`
		} `bson:"sub"`
		SubPtr *struct {
			A string `bson:"a"`
		} `bson:"subPtr"`
		Str   string                  `bson:"str"`
		Ptr   *int                    `bson:"ptr"`
		MDate rfctime.RFC3339NanoTime `bson:"mdate"`
	}

	coll := W[TestData](&mongo.Collection{})

	coll.init()

	t0 := time.Now()
	t1 := rfctime.NewRFC3339Nano(t0)

	d := TestData{
		ID:    "1",
		CDate: t0,
		Sub: struct {
			A string `bson:"a"`
		}{
			A: "2",
		},
		SubPtr: &struct {
			A string `bson:"a"`
		}{
			A: "4",
		},
		Str:   "3",
		Ptr:   langext.Ptr(4),
		MDate: t1,
	}

	gft := func(k string) fullTypeRef {
		v, err := coll.getFieldType(k)
		if err != nil {
			t.Errorf("%s: %v", "failed to getFieldType", err)
		}
		return v
	}

	gfv := func(k string) any {
		v, err := coll.getFieldValue(d, k)
		if err != nil {
			t.Errorf("%s: %v", "failed to getFieldType", err)
		}
		return v
	}

	tst.AssertEqual(t, gft("_id").Kind.String(), "string")
	tst.AssertEqual(t, gft("_id").Type.String(), "wmo.IDType")
	tst.AssertEqual(t, gft("_id").Name, "ID")
	tst.AssertEqual(t, gft("_id").IsPointer, false)
	tst.AssertEqual(t, gfv("_id").(IDType), "1")

	tst.AssertEqual(t, gft("cdate").Kind.String(), "struct")
	tst.AssertEqual(t, gft("cdate").Type.String(), "time.Time")
	tst.AssertEqual(t, gft("cdate").Name, "CDate")
	tst.AssertEqual(t, gft("cdate").IsPointer, false)
	tst.AssertEqual(t, gfv("cdate").(time.Time), t0)

	tst.AssertEqual(t, gft("sub.a").Kind.String(), "string")
	tst.AssertEqual(t, gft("sub.a").Type.String(), "string")
	tst.AssertEqual(t, gft("sub.a").Name, "A")
	tst.AssertEqual(t, gft("sub.a").IsPointer, false)
	tst.AssertEqual(t, gfv("sub.a").(string), "2")

	tst.AssertEqual(t, gft("subPtr.a").Kind.String(), "string")
	tst.AssertEqual(t, gft("subPtr.a").Type.String(), "string")
	tst.AssertEqual(t, gft("subPtr.a").Name, "A")
	tst.AssertEqual(t, gft("subPtr.a").IsPointer, false)
	tst.AssertEqual(t, gfv("subPtr.a").(string), "4")

	tst.AssertEqual(t, gft("str").Kind.String(), "string")
	tst.AssertEqual(t, gft("str").Type.String(), "string")
	tst.AssertEqual(t, gft("str").Name, "Str")
	tst.AssertEqual(t, gft("str").IsPointer, false)
	tst.AssertEqual(t, gfv("str").(string), "3")

	tst.AssertEqual(t, gft("ptr").Kind.String(), "int")
	tst.AssertEqual(t, gft("ptr").Type.String(), "int")
	tst.AssertEqual(t, gft("ptr").Name, "Ptr")
	tst.AssertEqual(t, gft("ptr").IsPointer, true)
	tst.AssertEqual(t, *gfv("ptr").(*int), 4)
}

func TestReflectionGetTokenValueAsMongoType(t *testing.T) {

	type IDType string

	type RecurseiveType struct {
		Other int             `bson:"other"`
		Inner *RecurseiveType `bson:"inner"`
	}

	type TestData struct {
		ID    IDType    `bson:"_id"`
		CDate time.Time `bson:"cdate"`
		Sub   struct {
			A string `bson:"a"`
		} `bson:"sub"`
		SubPtr *struct {
			A string `bson:"a"`
		} `bson:"subPtr"`
		Str   string                  `bson:"str"`
		Ptr   *int                    `bson:"ptr"`
		Num   int                     `bson:"num"`
		MDate rfctime.RFC3339NanoTime `bson:"mdate"`
		Rec   RecurseiveType          `bson:"rec"`
	}

	coll := W[TestData](&mongo.Collection{})

	coll.init()

	gtvasmt := func(value string, fieldName string) any {
		v, err := coll.getTokenValueAsMongoType(value, fieldName)
		if err != nil {
			t.Errorf("%s", "failed to getTokenValueAsMongoType")
			t.Errorf("%v+", err)
		}
		return v
	}

	tx, err := time.Parse(time.RFC3339Nano, "2009-11-10T23:00:00Z")
	if err != nil {
		t.Errorf("%v", err)
	}

	tst.AssertEqual(t, gtvasmt("hello", "str").(string), "hello")
	tst.AssertEqual(t, gtvasmt("hello", "sub.a").(string), "hello")
	tst.AssertEqual(t, gtvasmt("hello", "subPtr.a").(string), "hello")
	tst.AssertEqual(t, gtvasmt("4", "rec.other").(int), 4)
	tst.AssertEqual(t, gtvasmt("4", "num").(int), 4)
	tst.AssertEqual(t, gtvasmt("asdf", "_id").(IDType), "asdf")
	tst.AssertEqual(t, gtvasmt("", "ptr").(*int), nil)
	tst.AssertEqual(t, *(gtvasmt("123", "ptr").(*int)), 123)
	tst.AssertEqual(t, gtvasmt("2009-11-10T23:00:00Z", "cdate").(time.Time), tx)
	tst.AssertEqual(t, gtvasmt("2009-11-10T23:00:00Z", "mdate").(rfctime.RFC3339NanoTime), rfctime.NewRFC3339Nano(tx))
}

func TestReflectionGetFieldValueAsTokenString(t *testing.T) {

	type IDType string

	type TestData struct {
		ID    IDType    `bson:"_id"`
		CDate time.Time `bson:"cdate"`
		Sub   struct {
			A string `bson:"a"`
		} `bson:"sub"`
		Str   string                  `bson:"str"`
		Ptr   *int                    `bson:"ptr"`
		Num   int                     `bson:"num"`
		Ptr2  *int                    `bson:"ptr2"`
		FFF   float64                 `bson:"fff"`
		MDate rfctime.RFC3339NanoTime `bson:"mdate"`
	}

	coll := W[TestData](&mongo.Collection{})

	coll.init()

	t0 := time.Date(2000, 1, 1, 12, 0, 0, 0, timeext.TimezoneBerlin)
	t1 := rfctime.NewRFC3339Nano(t0)

	d := TestData{
		ID:    "1",
		CDate: t0,
		MDate: t1,
		Sub: struct {
			A string `bson:"a"`
		}{
			A: "2",
		},
		Str:  "3",
		Ptr:  langext.Ptr(4),
		Num:  22,
		FFF:  22.5,
		Ptr2: nil,
	}

	gfvats := func(value TestData, fieldName string) string {
		v, err := coll.getFieldValueAsTokenString(value, fieldName)
		if err != nil {
			t.Errorf("%s: %v", "failed to getTokenValueAsMongoType", err)
		}
		return v
	}

	tst.AssertEqual(t, gfvats(d, "str"), "3")
	tst.AssertEqual(t, gfvats(d, "num"), "22")
	tst.AssertEqual(t, gfvats(d, "_id"), "1")
	tst.AssertEqual(t, gfvats(d, "ptr"), "4")
	tst.AssertEqual(t, gfvats(d, "ptr2"), "")
	tst.AssertEqual(t, gfvats(d, "fff"), "22.5")
	tst.AssertEqual(t, gfvats(d, "cdate"), t0.Format(time.RFC3339Nano))
	tst.AssertEqual(t, gfvats(d, "mdate"), t0.Format(time.RFC3339Nano))
}

func TestReflectionWithInterface(t *testing.T) {

	type TestData struct {
		ID    primitive.ObjectID `bson:"_id"`
		CDate time.Time          `bson:"cdate"`
	}

	type TestInterface interface {
	}

	coll1 := W[TestInterface](&mongo.Collection{})

	tst.AssertTrue(t, coll1.coll != nil)
	tst.AssertEqual(t, 0, len(coll1.implDataTypeMap))

	df := func(ctx context.Context, dec Decodable) (TestInterface, error) {
		return TestData{}, nil
	}

	coll2 := W[TestInterface](&mongo.Collection{}).WithDecodeFunc(df, TestData{})

	tst.AssertTrue(t, coll2.coll != nil)
	tst.AssertEqual(t, 1, len(coll2.implDataTypeMap))

	tst.AssertEqual(t, "ID", coll2.implDataTypeMap[reflect.TypeOf(TestData{})]["_id"].Name)
	tst.AssertEqual(t, "CDate", coll2.implDataTypeMap[reflect.TypeOf(TestData{})]["cdate"].Name)
}