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"
)

type writeProtectionSchemaProvider struct {
	schema *models.ObjectMetadata
}

func (p writeProtectionSchemaProvider) GetSchema(context.Context, string) *models.ObjectMetadata {
	return p.schema
}

func TestRecordRepositoryRejectsCatalogAuthorityFieldsAcrossGenericWriteEntrypoints(t *testing.T) {
	operations := map[string]func(*RecordRepository, []models.SObject) error{
		"insert": func(repo *RecordRepository, records []models.SObject) error {
			return repo.Insert(context.Background(), constants.TableField, records[0])
		},
		"update": func(repo *RecordRepository, records []models.SObject) error {
			return repo.Update(context.Background(), constants.TableField, "field-1", records[0])
		},
		"bulk-parameterized": func(repo *RecordRepository, records []models.SObject) error {
			return repo.BulkInsert(context.Background(), constants.TableField, records[:1], nil, nil)
		},
		"bulk-raw": func(repo *RecordRepository, records []models.SObject) error {
			return repo.BulkInsert(context.Background(), constants.TableField, records, nil, nil)
		},
		"bulk-landing-parameterized": func(repo *RecordRepository, records []models.SObject) error {
			_, err := repo.BulkInsertWithLanding(
				context.Background(),
				constants.TableField,
				records[:1],
				nil,
				map[string]interface{}{"ignoreDuplicates": true},
			)
			return err
		},
		"bulk-landing-raw": func(repo *RecordRepository, records []models.SObject) error {
			_, err := repo.BulkInsertWithLanding(
				context.Background(),
				constants.TableField,
				records,
				nil,
				map[string]interface{}{"ignoreDuplicates": true},
			)
			return err
		},
	}

	for operationName, operation := range operations {
		t.Run(operationName, func(t *testing.T) {
			records := make([]models.SObject, 101)
			for i := range records {
				records[i] = models.SObject{
					constants.FieldSysField_WriteProtectionPolicy: nil,
				}
			}

			repo := NewRecordRepository(nil)
			repo.SetSchemaProvider(writeProtectionSchemaProvider{})
			err := operation(repo, records)
			if err == nil {
				t.Fatal("generic repository write accepted a catalog authority field")
			}
			var validationErr *apperrors.ValidationError
			if !errors.As(err, &validationErr) {
				t.Fatalf("error = %T %v, want *errors.ValidationError", err, err)
			}
			if !strings.Contains(err.Error(), constants.FieldWriteProtectionAuthorityOnly) {
				t.Fatalf("error = %v, want authority-only policy", err)
			}
		})
	}
}

func TestRecordRepositoryResolvesLogicalTableToCanonicalAuthorityPolicy(t *testing.T) {
	const logicalTable = "FieldMetadata"
	repo := NewRecordRepository(nil)
	repo.SetSchemaProvider(writeProtectionSchemaProvider{schema: &models.ObjectMetadata{
		APIName: logicalTable,
		ReadSource: &models.ObjectReadSourceConfig{
			Kind:          "physical_table",
			PhysicalTable: constants.TableField,
		},
	}})

	err := repo.Insert(context.Background(), logicalTable, models.SObject{
		strings.ToLower(constants.FieldSysField_WriteProtectionPolicy): "trusted_only",
	})
	if err == nil {
		t.Fatal("logical-table write accepted a catalog authority field")
	}
	var validationErr *apperrors.ValidationError
	if !errors.As(err, &validationErr) {
		t.Fatalf("error = %T %v, want *errors.ValidationError", err, err)
	}
}

func TestRecordRepositoryAllowsFieldsWithoutCanonicalAuthorityPolicy(t *testing.T) {
	err := RejectAuthorityOnlyFieldWrites(
		constants.TableField,
		nil,
		models.SObject{constants.FieldAPIName: "Subject__c"},
	)
	if err != nil {
		t.Fatalf("ordinary canonical field rejected: %v", err)
	}
}

func TestRecordRepositoryRejectsGenericFieldMetadataRemoval(t *testing.T) {
	operations := map[string]func(*RecordRepository) error{
		"soft delete update": func(repo *RecordRepository) error {
			return repo.Update(
				context.Background(),
				constants.TableField,
				"field-1",
				models.SObject{constants.FieldIsDeleted: 1},
			)
		},
		"delete": func(repo *RecordRepository) error {
			return repo.Delete(context.Background(), constants.TableField, "field-1")
		},
		"physical delete": func(repo *RecordRepository) error {
			return repo.PhysicalDelete(context.Background(), constants.TableField, "field-1")
		},
		"set delete": func(repo *RecordRepository) error {
			return repo.DeleteByField(
				context.Background(),
				constants.TableField,
				constants.FieldAPIName,
				constants.FieldSysField_WriteProtectionPolicy,
			)
		},
	}

	for name, operation := range operations {
		t.Run(name, func(t *testing.T) {
			repo := NewRecordRepository(nil)
			repo.SetSchemaProvider(writeProtectionSchemaProvider{})
			err := operation(repo)
			if err == nil {
				t.Fatal("generic record removal accepted field metadata lifecycle mutation")
			}
			var validationErr *apperrors.ValidationError
			if !errors.As(err, &validationErr) {
				t.Fatalf("error = %T %v, want *errors.ValidationError", err, err)
			}
		})
	}
}

func TestGenericFieldMetadataRemovalGuardResolvesLogicalAliasAndAllowsOrdinaryTables(t *testing.T) {
	const logicalTable = "FieldMetadata"
	schema := &models.ObjectMetadata{
		APIName: logicalTable,
		Fields: []models.FieldMetadata{{
			APIName:      constants.FieldIsDeleted,
			PhysicalName: "is_deleted",
		}},
		ReadSource: &models.ObjectReadSourceConfig{
			Kind:          "physical_table",
			PhysicalTable: constants.TableField,
		},
	}
	if err := RejectGenericFieldMetadataRemoval(logicalTable, schema); err == nil {
		t.Fatal("logical alias accepted generic field metadata removal")
	}
	repo := NewRecordRepository(nil)
	repo.SetSchemaProvider(writeProtectionSchemaProvider{schema: schema})
	if err := repo.Update(context.Background(), logicalTable, "field-1", models.SObject{"is_deleted": 1}); err == nil {
		t.Fatal("logical alias accepted physical IsDeleted mutation")
	}
	if err := RejectGenericFieldMetadataRemoval("Account", &models.ObjectMetadata{APIName: "Account"}); err != nil {
		t.Fatalf("ordinary record removal rejected: %v", err)
	}
}

func TestRecordTypeCatalogIdentityFieldsUseCanonicalAuthorityFence(t *testing.T) {
	for _, fieldName := range []string{
		constants.FieldSysRecordType_CatalogNamespaceID,
		constants.FieldSysRecordType_CatalogLocalKey,
	} {
		t.Run(fieldName, func(t *testing.T) {
			err := RejectAuthorityOnlyFieldWrites(
				constants.TableRecordType,
				nil,
				models.SObject{fieldName: "reserved-key"},
			)
			if err == nil {
				t.Fatalf("generic repository write accepted %s", fieldName)
			}
			var validationErr *apperrors.ValidationError
			if !errors.As(err, &validationErr) {
				t.Fatalf("error = %T %v, want *errors.ValidationError", err, err)
			}
		})
	}
}
