package bootstrapimpl

import (
	"strings"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/nexuscrm/kernel/pkg/schema"
)

func TestVerifyConnectorV2IngressReceiptAuthoritySchemaAcceptsExactCatalogShape(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()

	definitions, err := exactManagedAuthorityDefinitions(connectorV2IngressReceiptAuthorityTableNames)
	if err != nil {
		t.Fatalf("load V2 ingress receipt definitions: %v", err)
	}
	if len(definitions) != 3 {
		t.Fatalf("V2 ingress receipt definitions = %d, want 3", len(definitions))
	}
	expectConnectorServingEpochPhysicalColumns(mock, definitions)
	for _, definition := range definitions {
		for _, index := range exactManagedAuthorityRequiredIndexes(definition) {
			expectConnectorServingEpochExactIndex(mock, index, index.unique, index.columns)
		}
		expectConnectorServingEpochForeignKeys(mock, definition, nil)
	}

	if err := verifyConnectorV2IngressReceiptAuthoritySchema(db); err != nil {
		t.Fatalf("verify exact V2 ingress receipt schema: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("SQL expectations: %v", err)
	}
}

func TestVerifyConnectorV2IngressReceiptColumnsRejectExactShapeDrift(t *testing.T) {
	definitions, err := exactManagedAuthorityDefinitions(connectorV2IngressReceiptAuthorityTableNames)
	if err != nil {
		t.Fatalf("load V2 ingress receipt definitions: %v", err)
	}
	receipt := connectorServingEpochDefinitionForTest(t, definitions, "_System_ConnectorV2Receipt")
	component := connectorServingEpochDefinitionForTest(t, definitions, "_System_ConnectorV2ReceiptComponent")
	admissionKey := connectorServingEpochDefinitionForTest(t, definitions, "_System_ConnectorV2ReceiptAdmissionKey")

	tests := []struct {
		name       string
		definition schema.TableDefinition
		mutate     func(map[string]map[string]PhysicalColumnShape)
		wantDetail string
	}{
		{
			name:       "legacy receipt protocol width",
			definition: receipt,
			mutate: func(columns map[string]map[string]PhysicalColumnShape) {
				shape := columns[strings.ToLower(receipt.TableName)][strings.ToLower("ReceiptProtocolVersion")]
				shape.ColumnType = "varchar(255)"
				columns[strings.ToLower(receipt.TableName)][strings.ToLower("ReceiptProtocolVersion")] = shape
			},
			wantDetail: "ReceiptProtocolVersion",
		},
		{
			name:       "component payload carrier type",
			definition: component,
			mutate: func(columns map[string]map[string]PhysicalColumnShape) {
				shape := columns[strings.ToLower(component.TableName)][strings.ToLower("PayloadCiphertext")]
				shape.ColumnType = "longblob"
				columns[strings.ToLower(component.TableName)][strings.ToLower("PayloadCiphertext")] = shape
			},
			wantDetail: "PayloadCiphertext",
		},
		{
			name:       "receipt ciphertext carrier type",
			definition: receipt,
			mutate: func(columns map[string]map[string]PhysicalColumnShape) {
				shape := columns[strings.ToLower(receipt.TableName)][strings.ToLower("PayloadCiphertext")]
				shape.ColumnType = "longblob"
				columns[strings.ToLower(receipt.TableName)][strings.ToLower("PayloadCiphertext")] = shape
			},
			wantDetail: "PayloadCiphertext",
		},
		{
			name:       "nullable disposition tuple",
			definition: admissionKey,
			mutate: func(columns map[string]map[string]PhysicalColumnShape) {
				shape := columns[strings.ToLower(admissionKey.TableName)][strings.ToLower("Disposition")]
				shape.Nullable = true
				columns[strings.ToLower(admissionKey.TableName)][strings.ToLower("Disposition")] = shape
			},
			wantDetail: "Disposition",
		},
		{
			name:       "extra column",
			definition: receipt,
			mutate: func(columns map[string]map[string]PhysicalColumnShape) {
				columns[strings.ToLower(receipt.TableName)]["unexpected"] = PhysicalColumnShape{ColumnType: "bigint"}
			},
			wantDetail: "exact catalog-owned shape",
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			columns := connectorServingEpochPhysicalColumnShapes([]schema.TableDefinition{test.definition})
			test.mutate(columns)
			err := verifyExactManagedAuthorityColumns(columns, test.definition)
			if err == nil || !strings.Contains(err.Error(), test.wantDetail) || !strings.Contains(err.Error(), "cmd/setup apply") {
				t.Fatalf("column drift error = %v, want %q and apply guidance", err, test.wantDetail)
			}
		})
	}
}

func TestVerifyConnectorV2IngressReceiptIndexesRequireFullColumnPrimaryKey(t *testing.T) {
	definitions, err := exactManagedAuthorityDefinitions(connectorV2IngressReceiptAuthorityTableNames)
	if err != nil {
		t.Fatalf("load V2 ingress receipt definitions: %v", err)
	}
	receipt := connectorServingEpochDefinitionForTest(t, definitions, "_System_ConnectorV2Receipt")
	required := exactManagedAuthorityRequiredIndexes(receipt)
	if len(required) == 0 || required[0].indexName != "PRIMARY" || len(required[0].columns) != 1 || required[0].columns[0] != "Id" {
		t.Fatalf("receipt index requirements = %+v, want PRIMARY(Id) first", required)
	}

	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock.New: %v", err)
	}
	defer db.Close()
	// VerifyExactTableIndex excludes prefix indexes in SQL. Returning no rows
	// models a same-name PRIMARY(Id(16)) that must not satisfy readiness.
	expectConnectorServingEpochExactIndex(mock, required[0], true, nil)

	err = verifyExactManagedAuthorityIndexes(db, receipt)
	if err == nil ||
		!strings.Contains(err.Error(), "PRIMARY(Id)") ||
		!strings.Contains(err.Error(), "full-column") ||
		!strings.Contains(err.Error(), "cmd/setup apply") {
		t.Fatalf("prefix-primary readiness error = %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("SQL expectations: %v", err)
	}
}

func TestVerifyConnectorV2IngressReceiptForeignKeysRequireExactRestrictiveSet(t *testing.T) {
	definitions, err := exactManagedAuthorityDefinitions(connectorV2IngressReceiptAuthorityTableNames)
	if err != nil {
		t.Fatalf("load V2 ingress receipt definitions: %v", err)
	}
	admissionKey := connectorServingEpochDefinitionForTest(t, definitions, "_System_ConnectorV2ReceiptAdmissionKey")

	tests := []struct {
		name   string
		mutate func([]connectorServingEpochPhysicalForeignKey) []connectorServingEpochPhysicalForeignKey
	}{
		{
			name: "cascade",
			mutate: func(foreignKeys []connectorServingEpochPhysicalForeignKey) []connectorServingEpochPhysicalForeignKey {
				foreignKeys[0].deleteRule = "CASCADE"
				return foreignKeys
			},
		},
		{
			name: "cross schema",
			mutate: func(foreignKeys []connectorServingEpochPhysicalForeignKey) []connectorServingEpochPhysicalForeignKey {
				foreignKeys[1].referencedSchema = "other_database"
				return foreignKeys
			},
		},
		{
			name: "extra foreign key",
			mutate: func(foreignKeys []connectorServingEpochPhysicalForeignKey) []connectorServingEpochPhysicalForeignKey {
				return append(foreignKeys, foreignKeys[0])
			},
		},
	}

	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			db, mock, err := sqlmock.New()
			if err != nil {
				t.Fatalf("sqlmock.New: %v", err)
			}
			defer db.Close()

			physical := connectorServingEpochPhysicalForeignKeys(admissionKey)
			expectConnectorServingEpochForeignKeys(mock, admissionKey, test.mutate(physical))
			err = verifyExactManagedAuthorityForeignKeys(db, "Connector V2 ingress receipt authority", admissionKey)
			if err == nil || !strings.Contains(err.Error(), admissionKey.TableName) || !strings.Contains(err.Error(), "cmd/setup apply") {
				t.Fatalf("foreign-key drift error = %v", err)
			}
			if err := mock.ExpectationsWereMet(); err != nil {
				t.Fatalf("SQL expectations: %v", err)
			}
		})
	}
}

func TestConnectorV2IngressReceiptNullableReceiptReferenceIsCatalogOwned(t *testing.T) {
	definitions, err := exactManagedAuthorityDefinitions(connectorV2IngressReceiptAuthorityTableNames)
	if err != nil {
		t.Fatalf("load V2 ingress receipt definitions: %v", err)
	}
	admissionKey := connectorServingEpochDefinitionForTest(t, definitions, "_System_ConnectorV2ReceiptAdmissionKey")
	receiptID := connectorServingAuthorityColumn(t, admissionKey, "ReceiptId")
	if receiptID.Type != "VARCHAR(255)" || !receiptID.Nullable || receiptID.CollationName != "utf8mb4_bin" {
		t.Fatalf("ReceiptId = type:%q nullable:%t collation:%q, want VARCHAR(255)/true/utf8mb4_bin", receiptID.Type, receiptID.Nullable, receiptID.CollationName)
	}
	if receiptID.Default != "" {
		t.Fatalf("ReceiptId default = %q, want none", receiptID.Default)
	}
}
