package serving

import (
	"errors"
	"regexp"
	"strings"
	"testing"
)

func TestVerifyAccountServingGateSchemaAcceptsExactCatalogShape(t *testing.T) {
	db, mock := newServingSchemaMock(t)
	expectServingSchema(t, mock, exactAccountServingGateSchemaFixture(), 3)

	if err := verifyAccountServingGateSchema(t.Context(), db); err != nil {
		t.Fatalf("verify exact AccountServingGate schema: %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestVerifyAccountServingGateSchemaRejectsColumnIndexAndForeignKeyDrift(t *testing.T) {
	t.Run("missing column", func(t *testing.T) {
		db, mock := newServingSchemaMock(t)
		fixture := exactAccountServingGateSchemaFixture()
		fixture.tables[0].columns = fixture.tables[0].columns[:len(fixture.tables[0].columns)-1]
		expectServingColumns(mock, accountServingGateTable, fixture.tables[0].columns)
		requireAccountGateSchemaUnavailable(
			t,
			verifyAccountServingGateSchema(t.Context(), db),
			"missing column LastModifiedDate",
		)
	})

	t.Run("prefix unique index", func(t *testing.T) {
		db, mock := newServingSchemaMock(t)
		fixture := exactAccountServingGateSchemaFixture()
		fixture.tables[0].indexes[1].prefixAt = 1
		expectServingColumns(mock, accountServingGateTable, fixture.tables[0].columns)
		expectServingIndexes(mock, accountServingGateTable, fixture.tables[0].indexes)
		requireAccountGateSchemaUnavailable(
			t,
			verifyAccountServingGateSchema(t.Context(), db),
			"unsafe sequence, column, uniqueness, or prefix",
		)
	})

	t.Run("cross database cascade foreign key", func(t *testing.T) {
		db, mock := newServingSchemaMock(t)
		fixture := exactAccountServingGateSchemaFixture()
		fixture.tables[0].foreignKeys[0].referencedSchema = "other_database"
		fixture.tables[0].foreignKeys[0].deleteRule = "CASCADE"
		expectServingColumns(mock, accountServingGateTable, fixture.tables[0].columns)
		expectServingIndexes(mock, accountServingGateTable, fixture.tables[0].indexes)
		expectServingForeignKeys(mock, accountServingGateTable, fixture.tables[0].foreignKeys)
		requireAccountGateSchemaUnavailable(
			t,
			verifyAccountServingGateSchema(t.Context(), db),
			"other_database",
		)
	})
}

func TestVerifyAccountServingGateSchemaPreservesInspectionFailure(t *testing.T) {
	db, mock := newServingSchemaMock(t)
	cause := errors.New("metadata connection unavailable")
	mock.ExpectQuery(regexp.QuoteMeta(inspectServingColumnsSQL)).
		WithArgs(accountServingGateTable).
		WillReturnError(cause)

	err := verifyAccountServingGateSchema(t.Context(), db)
	if !errors.Is(err, ErrSchemaUnavailable) ||
		!errors.Is(err, errAccountServingGateSchemaUnavailable) ||
		!errors.Is(err, cause) {
		t.Fatalf("schema inspection error = %v, want all wrapped causes", err)
	}
}

func TestVerifyAccountServingGateSchemaRejectsMissingDependencies(t *testing.T) {
	if err := verifyAccountServingGateSchema(nil, nil); !errors.Is(err, ErrSchemaUnavailable) ||
		!errors.Is(err, errAccountServingGateSchemaUnavailable) {
		t.Fatalf("nil schema dependency error = %v", err)
	}
}

func exactAccountServingGateSchemaFixture() servingSchemaFixture {
	foreignKeys := make([]servingForeignKeyFixture, len(accountServingGateSchema.foreignKeys))
	for i, foreignKey := range accountServingGateSchema.foreignKeys {
		foreignKeys[i] = servingForeignKeyFixture{
			constraint:       "fk_account_serving_gate_epoch",
			column:           foreignKey.column,
			referencedSchema: servingSchemaFixtureDatabase,
			referencedTable:  foreignKey.referencedTable,
			referencedColumn: foreignKey.referencedColumn,
			currentSchema:    servingSchemaFixtureDatabase,
			updateRule:       "RESTRICT",
			deleteRule:       "RESTRICT",
		}
	}
	return servingSchemaFixture{tables: []servingTableSchemaFixture{{
		name:        accountServingGateSchema.name,
		columns:     servingColumnFixtures(accountServingGateSchema.columns),
		indexes:     servingIndexFixtures(accountServingGateSchema.indexes),
		foreignKeys: foreignKeys,
	}}}
}

func requireAccountGateSchemaUnavailable(t *testing.T, err error, detail string) {
	t.Helper()
	if !errors.Is(err, ErrSchemaUnavailable) || !errors.Is(err, errAccountServingGateSchemaUnavailable) {
		t.Fatalf("error = %v, want account gate schema unavailable", err)
	}
	if !strings.Contains(err.Error(), detail) {
		t.Fatalf("error = %v, want detail %q", err, detail)
	}
}
