package core_test

import (
	"context"
	"encoding/json"
	"fmt"
	"testing"

	"github.com/pocketbase/pocketbase/core"
	"github.com/pocketbase/pocketbase/tests"
	"github.com/pocketbase/pocketbase/tools/types"
)

func TestRelationFieldBaseMethods(t *testing.T) {
	testFieldBaseMethods(t, core.FieldTypeRelation)
}

func TestRelationFieldColumnType(t *testing.T) {
	app, _ := tests.NewTestApp()
	defer app.Cleanup()

	scenarios := []struct {
		name     string
		field    *core.RelationField
		expected string
	}{
		{
			"single (zero)",
			&core.RelationField{},
			"TEXT DEFAULT '' NOT NULL",
		},
		{
			"single",
			&core.RelationField{MaxSelect: 1},
			"TEXT DEFAULT '' NOT NULL",
		},
		{
			"multiple",
			&core.RelationField{MaxSelect: 2},
			"JSON DEFAULT '[]' NOT NULL",
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			if v := s.field.ColumnType(app); v != s.expected {
				t.Fatalf("Expected\n%q\ngot\n%q", s.expected, v)
			}
		})
	}
}

func TestRelationFieldIsMultiple(t *testing.T) {
	scenarios := []struct {
		name     string
		field    *core.RelationField
		expected bool
	}{
		{
			"zero",
			&core.RelationField{},
			false,
		},
		{
			"single",
			&core.RelationField{MaxSelect: 1},
			false,
		},
		{
			"multiple",
			&core.RelationField{MaxSelect: 2},
			true,
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			if v := s.field.IsMultiple(); v != s.expected {
				t.Fatalf("Expected %v, got %v", s.expected, v)
			}
		})
	}
}

func TestRelationFieldPrepareValue(t *testing.T) {
	app, _ := tests.NewTestApp()
	defer app.Cleanup()

	record := core.NewRecord(core.NewBaseCollection("test"))

	scenarios := []struct {
		raw      any
		field    *core.RelationField
		expected string
	}{
		// single
		{nil, &core.RelationField{MaxSelect: 1}, `""`},
		{"", &core.RelationField{MaxSelect: 1}, `""`},
		{123, &core.RelationField{MaxSelect: 1}, `"123"`},
		{"a", &core.RelationField{MaxSelect: 1}, `"a"`},
		{`["a"]`, &core.RelationField{MaxSelect: 1}, `"a"`},
		{[]string{}, &core.RelationField{MaxSelect: 1}, `""`},
		{[]string{"a", "b"}, &core.RelationField{MaxSelect: 1}, `"b"`},

		// multiple
		{nil, &core.RelationField{MaxSelect: 2}, `[]`},
		{"", &core.RelationField{MaxSelect: 2}, `[]`},
		{123, &core.RelationField{MaxSelect: 2}, `["123"]`},
		{"a", &core.RelationField{MaxSelect: 2}, `["a"]`},
		{`["a"]`, &core.RelationField{MaxSelect: 2}, `["a"]`},
		{[]string{}, &core.RelationField{MaxSelect: 2}, `[]`},
		{[]string{"a", "b", "c"}, &core.RelationField{MaxSelect: 2}, `["a","b","c"]`},
	}

	for i, s := range scenarios {
		t.Run(fmt.Sprintf("%d_%#v_%v", i, s.raw, s.field.IsMultiple()), func(t *testing.T) {
			v, err := s.field.PrepareValue(record, s.raw)
			if err != nil {
				t.Fatal(err)
			}

			vRaw, err := json.Marshal(v)
			if err != nil {
				t.Fatal(err)
			}

			if string(vRaw) != s.expected {
				t.Fatalf("Expected %q, got %q", s.expected, vRaw)
			}
		})
	}
}

func TestRelationFieldDriverValue(t *testing.T) {
	app, _ := tests.NewTestApp()
	defer app.Cleanup()

	scenarios := []struct {
		raw      any
		field    *core.RelationField
		expected string
	}{
		// single
		{nil, &core.RelationField{MaxSelect: 1}, `""`},
		{"", &core.RelationField{MaxSelect: 1}, `""`},
		{123, &core.RelationField{MaxSelect: 1}, `"123"`},
		{"a", &core.RelationField{MaxSelect: 1}, `"a"`},
		{`["a"]`, &core.RelationField{MaxSelect: 1}, `"a"`},
		{[]string{}, &core.RelationField{MaxSelect: 1}, `""`},
		{[]string{"a", "b"}, &core.RelationField{MaxSelect: 1}, `"b"`},

		// multiple
		{nil, &core.RelationField{MaxSelect: 2}, `[]`},
		{"", &core.RelationField{MaxSelect: 2}, `[]`},
		{123, &core.RelationField{MaxSelect: 2}, `["123"]`},
		{"a", &core.RelationField{MaxSelect: 2}, `["a"]`},
		{`["a"]`, &core.RelationField{MaxSelect: 2}, `["a"]`},
		{[]string{}, &core.RelationField{MaxSelect: 2}, `[]`},
		{[]string{"a", "b", "c"}, &core.RelationField{MaxSelect: 2}, `["a","b","c"]`},
	}

	for i, s := range scenarios {
		t.Run(fmt.Sprintf("%d_%#v_%v", i, s.raw, s.field.IsMultiple()), func(t *testing.T) {
			record := core.NewRecord(core.NewBaseCollection("test"))
			record.SetRaw(s.field.GetName(), s.raw)

			v, err := s.field.DriverValue(record)
			if err != nil {
				t.Fatal(err)
			}

			if s.field.IsMultiple() {
				_, ok := v.(types.JSONArray[string])
				if !ok {
					t.Fatalf("Expected types.JSONArray value, got %T", v)
				}
			} else {
				_, ok := v.(string)
				if !ok {
					t.Fatalf("Expected string value, got %T", v)
				}
			}

			vRaw, err := json.Marshal(v)
			if err != nil {
				t.Fatal(err)
			}

			if string(vRaw) != s.expected {
				t.Fatalf("Expected %q, got %q", s.expected, vRaw)
			}
		})
	}
}

func TestRelationFieldValidateValue(t *testing.T) {
	app, _ := tests.NewTestApp()
	defer app.Cleanup()

	demo1, err := app.FindCollectionByNameOrId("demo1")
	if err != nil {
		t.Fatal(err)
	}

	scenarios := []struct {
		name        string
		field       *core.RelationField
		record      func() *core.Record
		expectError bool
	}{
		// single
		{
			"[single] zero field value (not required)",
			&core.RelationField{Name: "test", MaxSelect: 1, CollectionId: demo1.Id},
			func() *core.Record {
				record := core.NewRecord(core.NewBaseCollection("test_collection"))
				record.SetRaw("test", "")
				return record
			},
			false,
		},
		{
			"[single] zero field value (required)",
			&core.RelationField{Name: "test", MaxSelect: 1, CollectionId: demo1.Id, Required: true},
			func() *core.Record {
				record := core.NewRecord(core.NewBaseCollection("test_collection"))
				record.SetRaw("test", "")
				return record
			},
			true,
		},
		{
			"[single] id from other collection",
			&core.RelationField{Name: "test", MaxSelect: 1, CollectionId: demo1.Id},
			func() *core.Record {
				record := core.NewRecord(core.NewBaseCollection("test_collection"))
				record.SetRaw("test", "achvryl401bhse3")
				return record
			},
			true,
		},
		{
			"[single] valid id",
			&core.RelationField{Name: "test", MaxSelect: 1, CollectionId: demo1.Id},
			func() *core.Record {
				record := core.NewRecord(core.NewBaseCollection("test_collection"))
				record.SetRaw("test", "84nmscqy84lsi1t")
				return record
			},
			false,
		},
		{
			"[single] > MaxSelect",
			&core.RelationField{Name: "test", MaxSelect: 1, CollectionId: demo1.Id},
			func() *core.Record {
				record := core.NewRecord(core.NewBaseCollection("test_collection"))
				record.SetRaw("test", []string{"84nmscqy84lsi1t", "al1h9ijdeojtsjy"})
				return record
			},
			true,
		},

		// multiple
		{
			"[multiple] zero field value (not required)",
			&core.RelationField{Name: "test", MaxSelect: 2, CollectionId: demo1.Id},
			func() *core.Record {
				record := core.NewRecord(core.NewBaseCollection("test_collection"))
				record.SetRaw("test", []string{})
				return record
			},
			false,
		},
		{
			"[multiple] zero field value (required)",
			&core.RelationField{Name: "test", MaxSelect: 2, CollectionId: demo1.Id, Required: true},
			func() *core.Record {
				record := core.NewRecord(core.NewBaseCollection("test_collection"))
				record.SetRaw("test", []string{})
				return record
			},
			true,
		},
		{
			"[multiple] id from other collection",
			&core.RelationField{Name: "test", MaxSelect: 2, CollectionId: demo1.Id},
			func() *core.Record {
				record := core.NewRecord(core.NewBaseCollection("test_collection"))
				record.SetRaw("test", []string{"84nmscqy84lsi1t", "achvryl401bhse3"})
				return record
			},
			true,
		},
		{
			"[multiple] valid id",
			&core.RelationField{Name: "test", MaxSelect: 2, CollectionId: demo1.Id},
			func() *core.Record {
				record := core.NewRecord(core.NewBaseCollection("test_collection"))
				record.SetRaw("test", []string{"84nmscqy84lsi1t", "al1h9ijdeojtsjy"})
				return record
			},
			false,
		},
		{
			"[multiple] > MaxSelect",
			&core.RelationField{Name: "test", MaxSelect: 2, CollectionId: demo1.Id},
			func() *core.Record {
				record := core.NewRecord(core.NewBaseCollection("test_collection"))
				record.SetRaw("test", []string{"84nmscqy84lsi1t", "al1h9ijdeojtsjy", "imy661ixudk5izi"})
				return record
			},
			true,
		},
		{
			"[multiple] < MinSelect",
			&core.RelationField{Name: "test", MinSelect: 2, MaxSelect: 99, CollectionId: demo1.Id},
			func() *core.Record {
				record := core.NewRecord(core.NewBaseCollection("test_collection"))
				record.SetRaw("test", []string{"84nmscqy84lsi1t"})
				return record
			},
			true,
		},
		{
			"[multiple] >= MinSelect",
			&core.RelationField{Name: "test", MinSelect: 2, MaxSelect: 99, CollectionId: demo1.Id},
			func() *core.Record {
				record := core.NewRecord(core.NewBaseCollection("test_collection"))
				record.SetRaw("test", []string{"84nmscqy84lsi1t", "al1h9ijdeojtsjy", "imy661ixudk5izi"})
				return record
			},
			false,
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			err := s.field.ValidateValue(context.Background(), app, s.record())

			hasErr := err != nil
			if hasErr != s.expectError {
				t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectError, hasErr, err)
			}
		})
	}
}

func TestRelationFieldValidateSettings(t *testing.T) {
	testDefaultFieldIdValidation(t, core.FieldTypeRelation)
	testDefaultFieldNameValidation(t, core.FieldTypeRelation)

	app, _ := tests.NewTestApp()
	defer app.Cleanup()

	demo1, err := app.FindCollectionByNameOrId("demo1")
	if err != nil {
		t.Fatal(err)
	}

	scenarios := []struct {
		name         string
		field        func(col *core.Collection) *core.RelationField
		expectErrors []string
	}{
		{
			"zero minimal",
			func(col *core.Collection) *core.RelationField {
				return &core.RelationField{
					Id:   "test",
					Name: "test",
				}
			},
			[]string{"collectionId"},
		},
		{
			"invalid collectionId",
			func(col *core.Collection) *core.RelationField {
				return &core.RelationField{
					Id:           "test",
					Name:         "test",
					CollectionId: demo1.Name,
				}
			},
			[]string{"collectionId"},
		},
		{
			"valid collectionId",
			func(col *core.Collection) *core.RelationField {
				return &core.RelationField{
					Id:           "test",
					Name:         "test",
					CollectionId: demo1.Id,
				}
			},
			[]string{},
		},
		{
			"base->view",
			func(col *core.Collection) *core.RelationField {
				return &core.RelationField{
					Id:           "test",
					Name:         "test",
					CollectionId: "v9gwnfh02gjq1q0",
				}
			},
			[]string{"collectionId"},
		},
		{
			"view->view",
			func(col *core.Collection) *core.RelationField {
				col.Type = core.CollectionTypeView
				return &core.RelationField{
					Id:           "test",
					Name:         "test",
					CollectionId: "v9gwnfh02gjq1q0",
				}
			},
			[]string{},
		},
		{
			"MinSelect < 0",
			func(col *core.Collection) *core.RelationField {
				return &core.RelationField{
					Id:           "test",
					Name:         "test",
					CollectionId: demo1.Id,
					MinSelect:    -1,
				}
			},
			[]string{"minSelect"},
		},
		{
			"MinSelect > 0",
			func(col *core.Collection) *core.RelationField {
				return &core.RelationField{
					Id:           "test",
					Name:         "test",
					CollectionId: demo1.Id,
					MinSelect:    1,
				}
			},
			[]string{"maxSelect"},
		},
		{
			"MaxSelect < MinSelect",
			func(col *core.Collection) *core.RelationField {
				return &core.RelationField{
					Id:           "test",
					Name:         "test",
					CollectionId: demo1.Id,
					MinSelect:    2,
					MaxSelect:    1,
				}
			},
			[]string{"maxSelect"},
		},
		{
			"MaxSelect >= MinSelect",
			func(col *core.Collection) *core.RelationField {
				return &core.RelationField{
					Id:           "test",
					Name:         "test",
					CollectionId: demo1.Id,
					MinSelect:    2,
					MaxSelect:    2,
				}
			},
			[]string{},
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			collection := core.NewBaseCollection("test_collection")
			collection.Fields.GetByName("id").SetId("test") // set a dummy known id so that it can be replaced

			field := s.field(collection)

			collection.Fields.Add(field)

			errs := field.ValidateSettings(context.Background(), app, collection)

			tests.TestValidationErrors(t, errs, s.expectErrors)
		})
	}
}

func TestRelationFieldFindSetter(t *testing.T) {
	scenarios := []struct {
		name      string
		key       string
		value     any
		field     *core.RelationField
		hasSetter bool
		expected  string
	}{
		{
			"no match",
			"example",
			"b",
			&core.RelationField{Name: "test", MaxSelect: 1},
			false,
			"",
		},
		{
			"exact match (single)",
			"test",
			"b",
			&core.RelationField{Name: "test", MaxSelect: 1},
			true,
			`"b"`,
		},
		{
			"exact match (multiple)",
			"test",
			[]string{"a", "b"},
			&core.RelationField{Name: "test", MaxSelect: 2},
			true,
			`["a","b"]`,
		},
		{
			"append (single)",
			"test+",
			"b",
			&core.RelationField{Name: "test", MaxSelect: 1},
			true,
			`"b"`,
		},
		{
			"append (multiple)",
			"test+",
			[]string{"a"},
			&core.RelationField{Name: "test", MaxSelect: 2},
			true,
			`["c","d","a"]`,
		},
		{
			"prepend (single)",
			"+test",
			"b",
			&core.RelationField{Name: "test", MaxSelect: 1},
			true,
			`"d"`, // the last of the existing values
		},
		{
			"prepend (multiple)",
			"+test",
			[]string{"a"},
			&core.RelationField{Name: "test", MaxSelect: 2},
			true,
			`["a","c","d"]`,
		},
		{
			"subtract (single)",
			"test-",
			"d",
			&core.RelationField{Name: "test", MaxSelect: 1},
			true,
			`"c"`,
		},
		{
			"subtract (multiple)",
			"test-",
			[]string{"unknown", "c"},
			&core.RelationField{Name: "test", MaxSelect: 2},
			true,
			`["d"]`,
		},
	}

	for _, s := range scenarios {
		t.Run(s.name, func(t *testing.T) {
			collection := core.NewBaseCollection("test_collection")
			collection.Fields.Add(s.field)

			setter := s.field.FindSetter(s.key)

			hasSetter := setter != nil
			if hasSetter != s.hasSetter {
				t.Fatalf("Expected hasSetter %v, got %v", s.hasSetter, hasSetter)
			}

			if !hasSetter {
				return
			}

			record := core.NewRecord(collection)
			record.SetRaw(s.field.GetName(), []string{"c", "d"})

			setter(record, s.value)

			raw, err := json.Marshal(record.Get(s.field.GetName()))
			if err != nil {
				t.Fatal(err)
			}
			rawStr := string(raw)

			if rawStr != s.expected {
				t.Fatalf("Expected %q, got %q", s.expected, rawStr)
			}
		})
	}
}