package persistence

import (
	"context"
	"errors"
	"strings"
	"testing"

	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestRecordRepositoryLogicalMutationFailsClosedWithoutAuthoritativeSchema(t *testing.T) {
	operations := map[string]func(*RecordRepository) error{
		"insert": func(repo *RecordRepository) error {
			return repo.Insert(
				context.Background(),
				"RecordType",
				models.SObject{constants.FieldID: "record-type-1"},
			)
		},
		"update": func(repo *RecordRepository) error {
			return repo.Update(
				context.Background(),
				"RecordType",
				"record-type-1",
				models.SObject{constants.FieldSysRecordType_Description: "updated"},
			)
		},
		"delete": func(repo *RecordRepository) error {
			return repo.Delete(context.Background(), "RecordType", "record-type-1")
		},
		"physical delete": func(repo *RecordRepository) error {
			return repo.PhysicalDelete(context.Background(), "RecordType", "record-type-1")
		},
		"set delete": func(repo *RecordRepository) error {
			return repo.DeleteByField(
				context.Background(),
				"RecordType",
				constants.FieldObjectID,
				"object-1",
			)
		},
		"bulk insert": func(repo *RecordRepository) error {
			return repo.BulkInsert(
				context.Background(),
				"RecordType",
				[]models.SObject{{constants.FieldID: "record-type-1"}},
				[]string{constants.FieldID},
				nil,
			)
		},
		"bulk landing": func(repo *RecordRepository) error {
			_, err := repo.BulkInsertWithLanding(
				context.Background(),
				"RecordType",
				[]models.SObject{{constants.FieldID: "record-type-1"}},
				[]string{constants.FieldID},
				map[string]interface{}{"ignoreDuplicates": true},
			)
			return err
		},
	}

	for name, operation := range operations {
		t.Run(name, func(t *testing.T) {
			repo := NewRecordRepository(nil)
			repo.SetSchemaProvider(writeProtectionSchemaProvider{})
			err := operation(repo)
			if err == nil || !strings.Contains(err.Error(), "authoritative schema") {
				t.Fatalf("logical mutation error = %v, want authoritative-schema failure", err)
			}
		})
	}
}

func TestRecordRepositoryEmptyBulkMutationRemainsANoOpWithoutSchema(t *testing.T) {
	repo := NewRecordRepository(nil)
	repo.SetSchemaProvider(writeProtectionSchemaProvider{})
	if err := repo.BulkInsert(context.Background(), "RecordType", nil, nil, nil); err != nil {
		t.Fatalf("BulkInsert(empty): %v", err)
	}
	landed, err := repo.BulkInsertWithLanding(context.Background(), "RecordType", nil, nil, nil)
	if err != nil {
		t.Fatalf("BulkInsertWithLanding(empty): %v", err)
	}
	if len(landed) != 0 {
		t.Fatalf("BulkInsertWithLanding(empty) = %v, want empty", landed)
	}
}

func TestRecordRepositoryLogicalAliasRejectsAuthorityFieldsBeforeDML(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName: "RecordType",
		ReadSource: &models.ObjectReadSourceConfig{
			Kind:          "physical_table",
			PhysicalTable: constants.TableRecordType,
		},
	}
	operations := map[string]func(*RecordRepository, string) error{
		"insert": func(repo *RecordRepository, fieldName string) error {
			return repo.Insert(
				context.Background(),
				"RecordType",
				models.SObject{fieldName: []byte("reserved")},
			)
		},
		"update": func(repo *RecordRepository, fieldName string) error {
			return repo.Update(
				context.Background(),
				"RecordType",
				"record-type-1",
				models.SObject{fieldName: []byte("reserved")},
			)
		},
		"bulk insert": func(repo *RecordRepository, fieldName string) error {
			return repo.BulkInsert(
				context.Background(),
				"RecordType",
				[]models.SObject{{fieldName: []byte("reserved")}},
				[]string{fieldName},
				nil,
			)
		},
		"bulk landing": func(repo *RecordRepository, fieldName string) error {
			_, err := repo.BulkInsertWithLanding(
				context.Background(),
				"RecordType",
				[]models.SObject{{fieldName: []byte("reserved")}},
				[]string{fieldName},
				map[string]interface{}{"ignoreDuplicates": true},
			)
			return err
		},
	}
	for _, fieldName := range []string{
		constants.FieldSysRecordType_CatalogNamespaceID,
		strings.ToLower(constants.FieldSysRecordType_CatalogNamespaceID),
		constants.FieldSysRecordType_CatalogLocalKey,
		strings.ToLower(constants.FieldSysRecordType_CatalogLocalKey),
	} {
		t.Run(fieldName, func(t *testing.T) {
			for name, operation := range operations {
				t.Run(name, func(t *testing.T) {
					repo := NewRecordRepository(nil)
					repo.SetSchemaProvider(writeProtectionSchemaProvider{schema: schema})
					err := operation(repo, fieldName)
					if err == nil {
						t.Fatal("logical alias accepted a catalog authority field")
					}
					var validationErr *apperrors.ValidationError
					if !errors.As(err, &validationErr) {
						t.Fatalf("error = %T %v, want validation error", err, err)
					}
				})
			}
		})
	}
}
