package persistence

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

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

func TestCatalogAuthorityProjectionIsExactAndCatalogOnly(t *testing.T) {
	tests := []struct {
		name      string
		tableName string
		field     models.FieldMetadata
		wantErr   bool
	}{
		{
			name:      "exact catalog projection",
			tableName: constants.TableField,
			field: models.FieldMetadata{
				APIName:               constants.FieldSysField_WriteProtectionPolicy,
				WriteProtectionPolicy: constants.FieldWriteProtectionAuthorityOnly,
			},
		},
		{
			name:      "exact explicit physical projection",
			tableName: constants.TableField,
			field: models.FieldMetadata{
				APIName:               constants.FieldSysField_WriteProtectionPolicy,
				PhysicalName:          constants.FieldSysField_WriteProtectionPolicy,
				WriteProtectionPolicy: constants.FieldWriteProtectionAuthorityOnly,
			},
		},
		{
			name:      "untrusted logical alias cannot borrow protected physical alias",
			tableName: constants.TableField,
			field: models.FieldMetadata{
				APIName:               "UntrustedAlias",
				PhysicalName:          constants.FieldSysField_WriteProtectionPolicy,
				WriteProtectionPolicy: constants.FieldWriteProtectionAuthorityOnly,
			},
			wantErr: true,
		},
		{
			name:      "protected logical alias cannot redirect to untrusted physical alias",
			tableName: constants.TableField,
			field: models.FieldMetadata{
				APIName:               constants.FieldSysField_WriteProtectionPolicy,
				PhysicalName:          "UntrustedPhysical",
				WriteProtectionPolicy: constants.FieldWriteProtectionAuthorityOnly,
			},
			wantErr: true,
		},
		{
			name:      "canonical declaration cannot be omitted",
			tableName: constants.TableField,
			field: models.FieldMetadata{
				APIName: constants.FieldSysField_WriteProtectionPolicy,
			},
			wantErr: true,
		},
		{
			name:      "noncatalog field cannot introduce authority policy",
			tableName: "Widget__c",
			field: models.FieldMetadata{
				APIName:               "AuthorityValue__c",
				WriteProtectionPolicy: constants.FieldWriteProtectionAuthorityOnly,
			},
			wantErr: true,
		},
		{
			name:      "ordinary metadata remains generic",
			tableName: "Widget__c",
			field: models.FieldMetadata{
				APIName:               "TrustedValue__c",
				WriteProtectionPolicy: constants.FieldWriteProtectionTrustedOnly,
			},
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			err := validateCatalogWriteProtectionProjection(tc.tableName, &tc.field)
			if tc.wantErr && err == nil {
				t.Fatal("validation succeeded, want error")
			}
			if !tc.wantErr && err != nil {
				t.Fatalf("validation failed: %v", err)
			}
		})
	}
}

func TestBatchCatalogAuthorityProjectionAllowsFieldOnlyContext(t *testing.T) {
	repo := NewSchemaRepository(nil, nil)
	err := repo.validateCatalogFieldContextWriteProtectionProjection(schema.FieldWithContext{
		Field: &models.FieldMetadata{
			APIName: "RuntimeField",
		},
		TableName: "Widget__c",
	})
	if err != nil {
		t.Fatalf("field-only context validation failed: %v", err)
	}
}

func TestBatchCatalogAuthorityProjectionLeavesOrdinaryAliasesToExistingConvergence(t *testing.T) {
	repo := NewSchemaRepository(nil, nil)
	err := repo.validateCatalogFieldContextWriteProtectionProjection(schema.FieldWithContext{
		Field: &models.FieldMetadata{
			APIName:      "LogicalAlias",
			PhysicalName: "physical_name",
		},
		ColumnDefinition: schema.ColumnDefinition{
			Name:         "physical_name",
			PhysicalName: "physical_name",
		},
		TableName: "Widget__c",
	})
	if err != nil {
		t.Fatalf("ordinary aliased context validation failed: %v", err)
	}
}

func TestGenericSchemaEntrypointsRejectNoncatalogAuthorityBeforeDatabaseWork(t *testing.T) {
	repo := NewSchemaRepository(nil, nil)
	column := schema.ColumnDefinition{
		Name:                  "AuthorityValue__c",
		Type:                  "VARCHAR(255)",
		WriteProtectionPolicy: constants.FieldWriteProtectionAuthorityOnly,
	}
	field := models.FieldMetadata{
		APIName:               column.Name,
		Type:                  constants.FieldTypeString,
		WriteProtectionPolicy: constants.FieldWriteProtectionAuthorityOnly,
	}
	operations := map[string]func() error{
		"create physical table wrapper": func() error {
			return repo.CreatePhysicalTable(context.Background(), schema.TableDefinition{
				TableName: "Widget__c",
				Columns:   []schema.ColumnDefinition{column},
			})
		},
		"create physical table inner": func() error {
			return repo.createPhysicalTable(context.Background(), schema.TableDefinition{
				TableName: "Widget__c",
				Columns:   []schema.ColumnDefinition{column},
			}, false)
		},
		"add column": func() error {
			return repo.AddColumn("Widget__c", column)
		},
		"modify column": func() error {
			return repo.ModifyColumn("Widget__c", column.Name, column)
		},
		"batch add columns": func() error {
			return repo.BatchAddColumns("Widget__c", []models.FieldMetadata{field})
		},
		"register field only": func() error {
			return repo.RegisterFieldOnly("Widget__c", column)
		},
		"pack physical column": func() error {
			return repo.ensurePackOwnedPhysicalColumn(context.Background(), "Widget__c", column)
		},
		"save field metadata": func() error {
			return repo.SaveFieldMetadataWithIDs(
				context.Background(),
				&field,
				"object-1",
				"Widget__c",
				"field-1",
				nil,
			)
		},
	}

	for name, operation := range operations {
		t.Run(name, func(t *testing.T) {
			err := operation()
			if err == nil {
				t.Fatal("generic schema entrypoint accepted noncatalog authority policy")
			}
			var validationErr *apperrors.ValidationError
			if !errors.As(err, &validationErr) {
				t.Fatalf("error = %T %v, want *errors.ValidationError", err, err)
			}
		})
	}
}

func TestGenericSchemaDropEntrypointsRejectCatalogAuthorityBeforeDatabaseWork(t *testing.T) {
	repo := NewSchemaRepository(nil, nil)
	operations := map[string]func() error{
		"drop column": func() error {
			return repo.DropColumn(
				constants.TableField,
				constants.FieldSysField_WriteProtectionPolicy,
			)
		},
		"drop column phase 1": func() error {
			return repo.DropColumnPhase1(
				constants.TableField,
				constants.FieldSysField_WriteProtectionPolicy,
				"platform",
			)
		},
		"drop column phase 2": func() error {
			return repo.DropColumnPhase2(
				constants.TableField,
				constants.FieldSysField_WriteProtectionPolicy,
				"platform",
			)
		},
		"physical removal owner": func() error {
			return repo.dropPhysicalColumnIfPresent(
				constants.TableField,
				constants.FieldSysField_WriteProtectionPolicy,
			)
		},
		"metadata removal owner": func() error {
			return repo.deleteFieldMetadataPreservingHistoryAuthority(
				context.Background(),
				constants.TableField,
				constants.FieldSysField_WriteProtectionPolicy,
				"",
				"",
			)
		},
	}

	for name, operation := range operations {
		t.Run(name, func(t *testing.T) {
			err := operation()
			if err == nil {
				t.Fatal("generic schema drop accepted catalog-owned authority field")
			}
			var validationErr *apperrors.ValidationError
			if !errors.As(err, &validationErr) {
				t.Fatalf("error = %T %v, want *errors.ValidationError", err, err)
			}
			if validationErr.Field != "columnName" ||
				!strings.Contains(validationErr.Message, "catalog-owned authority metadata") {
				t.Fatalf("validation error = %+v, want catalog authority drop rejection", validationErr)
			}
		})
	}
}

func TestGenericTableDropEntrypointsRejectCatalogAuthorityBeforeDatabaseWork(t *testing.T) {
	repo := NewSchemaRepository(nil, nil)
	operations := map[string]func() error{
		"drop table": func() error {
			return repo.DropTable("_sYsTeM_fIeLd")
		},
		"drop table owner": func() error {
			return repo.dropTable(context.Background(), "_SYSTEM_FIELD")
		},
		"metadata purge owner": func() error {
			return repo.dropTableMetadata(context.Background(), "_system_field")
		},
	}

	for name, operation := range operations {
		t.Run(name, func(t *testing.T) {
			err := operation()
			if err == nil {
				t.Fatal("generic table drop accepted a table containing catalog-owned authority metadata")
			}
			var validationErr *apperrors.ValidationError
			if !errors.As(err, &validationErr) {
				t.Fatalf("error = %T %v, want *errors.ValidationError", err, err)
			}
			if validationErr.Field != "tableName" ||
				!strings.Contains(validationErr.Message, "catalog-owned authority metadata") {
				t.Fatalf("validation error = %+v, want catalog authority table-drop rejection", validationErr)
			}
		})
	}
}

func TestCatalogAuthorityTableDropGuardAllowsTablesWithoutAuthorityPolicy(t *testing.T) {
	for _, tableName := range []string{"Widget__c", constants.TablePermissionSet} {
		t.Run(tableName, func(t *testing.T) {
			if err := rejectCatalogAuthorityTableDrop(tableName); err != nil {
				t.Fatalf("ordinary table drop rejected: %v", err)
			}
		})
	}
}

func TestSchemaUpsertPreservesAuthorityPolicyAndDeniedCapabilities(t *testing.T) {
	updates := make(map[string]interface{})
	repo := NewSchemaRepository(nil, nil)
	repo.configureBatchUpdatesGuarded(
		updates,
		[]string{
			constants.FieldSysField_WriteProtectionPolicy,
			constants.FieldSysField_Createable,
			constants.FieldSysField_Updateable,
		},
		nil,
	)

	for _, fieldName := range []string{
		constants.FieldSysField_WriteProtectionPolicy,
		constants.FieldSysField_Createable,
		constants.FieldSysField_Updateable,
	} {
		raw, ok := updates[fieldName].(query.Raw)
		if !ok {
			t.Fatalf("%s update = %T, want query.Raw", fieldName, updates[fieldName])
		}
		if !strings.Contains(string(raw), constants.FieldWriteProtectionAuthorityOnly) {
			t.Fatalf("%s update = %q, want authority-only predicate", fieldName, raw)
		}
	}
}

func TestWriteProtectionPolicyUpdateCombinesAuthorityAndCustomizationGuards(t *testing.T) {
	repo := NewSchemaRepository(nil, nil)
	for _, tc := range []struct {
		name    string
		guarded []string
		want    string
	}{
		{
			name: "unguarded admin authoring",
			want: "IF(COALESCE(`WriteProtectionPolicy`, '') = 'authority_only', `WriteProtectionPolicy`, VALUES(`WriteProtectionPolicy`))",
		},
		{
			name:    "guarded batch reapply",
			guarded: []string{constants.FieldSysField_WriteProtectionPolicy},
			want:    "IF(COALESCE(`WriteProtectionPolicy`, '') = 'authority_only', `WriteProtectionPolicy`, IF(COALESCE(VALUES(`WriteProtectionPolicy`), '') = 'authority_only', VALUES(`WriteProtectionPolicy`), IF(`IsCustomized` = 0 OR `IsCustomized` IS NULL, VALUES(`WriteProtectionPolicy`), `WriteProtectionPolicy`)))",
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			updates := make(map[string]interface{})
			repo.configureBatchUpdatesGuarded(
				updates,
				[]string{constants.FieldSysField_WriteProtectionPolicy},
				tc.guarded,
			)
			raw, ok := updates[constants.FieldSysField_WriteProtectionPolicy].(query.Raw)
			if !ok {
				t.Fatalf("write protection update = %T, want query.Raw", updates[constants.FieldSysField_WriteProtectionPolicy])
			}
			if string(raw) != tc.want {
				t.Fatalf("write protection update = %q, want %q", raw, tc.want)
			}
		})
	}
}
