package releasegate_test

import (
	"context"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/google/uuid"
	"gorm.io/gorm"

	"github.com/break/junhong_cmp_fiber/internal/infrastructure/releasegate"
	"github.com/break/junhong_cmp_fiber/internal/testutil"
)

func TestPublicFoundationMigrationsOnEmptyAndCompatibleDatabase(t *testing.T) {
	db := testutil.NewPostgresTransaction(t)
	schema := prepareMigrationSchema(t, db, 164)
	checker := releasegate.NewCheckerWithSchema(db, schema)

	pre, err := checker.Run(context.Background(), releasegate.PhasePre)
	if err != nil {
		t.Fatalf("执行迁移前检查失败：%v", err)
	}
	if !pre.Passed() {
		t.Fatalf("空数据库迁移前检查不应阻断：%+v", pre.Findings)
	}
	if err := db.Exec("CREATE TABLE compatible_existing_data (id bigint PRIMARY KEY, value text NOT NULL)").Error; err != nil {
		t.Fatalf("准备兼容存量表失败：%v", err)
	}
	if err := db.Exec("INSERT INTO compatible_existing_data (id, value) VALUES (1, 'keep')").Error; err != nil {
		t.Fatalf("准备兼容存量数据失败：%v", err)
	}

	executeMigration(t, db, "000165_create_public_outbox.up.sql")
	executeMigration(t, db, "000166_create_system_config.up.sql")
	if err := db.Exec("UPDATE schema_migrations SET version = 166, dirty = false").Error; err != nil {
		t.Fatalf("更新隔离迁移版本失败：%v", err)
	}

	post, err := checker.Run(context.Background(), releasegate.PhasePost)
	if err != nil {
		t.Fatalf("执行迁移后检查失败：%v", err)
	}
	if !post.Passed() {
		t.Fatalf("迁移后检查应通过：%+v", post.Findings)
	}
	postAgain, err := checker.Run(context.Background(), releasegate.PhasePost)
	if err != nil || !postAgain.Passed() {
		t.Fatalf("迁移后检查必须可重复执行：%v，结果：%+v", err, postAgain.Findings)
	}
	var compatibleCount int64
	if err := db.Table("compatible_existing_data").Count(&compatibleCount).Error; err != nil || compatibleCount != 1 {
		t.Fatalf("迁移破坏了兼容存量数据：%v，数量：%d", err, compatibleCount)
	}

	if err := db.Transaction(func(tx *gorm.DB) error {
		return executeMigrationWithError(tx, "000165_create_public_outbox.up.sql")
	}); err == nil {
		t.Fatal("公共对象已创建时不得通过重复 DDL 静默掩盖定义")
	}
}

func TestMigrationGateBlocksAnomaliesWithoutDestructiveWrites(t *testing.T) {
	db := testutil.NewPostgresTransaction(t)
	schema := prepareMigrationSchema(t, db, 166)
	executeMigration(t, db, "000165_create_public_outbox.up.sql")
	executeMigration(t, db, "000166_create_system_config.up.sql")
	if err := db.Exec(`INSERT INTO tb_outbox_event
		(event_id, event_type, payload_version, aggregate_type, aggregate_id, resource_type, resource_id, payload)
		VALUES ('blocked-event', 'foundation.blocked', 1, 'example', '1', 'example', '1', '{}'::jsonb)`).Error; err != nil {
		t.Fatalf("准备未投递异常失败：%v", err)
	}
	if err := db.Exec("ALTER TABLE tb_outbox_event DROP CONSTRAINT ck_outbox_event_status").Error; err != nil {
		t.Fatalf("准备非法状态定义失败：%v", err)
	}
	if err := db.Exec(`INSERT INTO tb_outbox_event
		(event_id, event_type, payload_version, aggregate_type, aggregate_id, resource_type, resource_id, payload, status)
		VALUES ('invalid-status', 'foundation.invalid', 1, 'example', '2', 'example', '2', '{}'::jsonb, 9)`).Error; err != nil {
		t.Fatalf("准备非法状态数据失败：%v", err)
	}

	report, err := releasegate.NewCheckerWithSchema(db, schema).Run(context.Background(), releasegate.PhasePost)
	if err != nil {
		t.Fatalf("执行异常门禁失败：%v", err)
	}
	if report.Passed() || !hasFinding(report, "OUTBOX_UNDELIVERED") || !hasFinding(report, "OUTBOX_INVALID_STATUS") || !hasFinding(report, "FOUNDATION_CONSTRAINT_MISSING") {
		t.Fatalf("异常数据和定义未被完整阻断：%+v", report.Findings)
	}
	var count int64
	if err := db.Table("tb_outbox_event").Count(&count).Error; err != nil || count != 2 {
		t.Fatalf("只读门禁修改了异常数据：%v，数量：%d", err, count)
	}
}

func TestDownMigrationAllowsEmptyStructuresAndRejectsExistingFacts(t *testing.T) {
	t.Run("空结构可回滚", func(t *testing.T) {
		db := testutil.NewPostgresTransaction(t)
		schema := prepareMigrationSchema(t, db, 166)
		executeMigration(t, db, "000165_create_public_outbox.up.sql")
		executeMigration(t, db, "000166_create_system_config.up.sql")
		executeMigration(t, db, "000166_create_system_config.down.sql")
		executeMigration(t, db, "000165_create_public_outbox.down.sql")
		for _, table := range []string{"tb_outbox_event", "tb_system_config"} {
			var exists bool
			if err := db.Raw("SELECT to_regclass(?) IS NOT NULL", schema+"."+table).Scan(&exists).Error; err != nil || exists {
				t.Fatalf("空结构回滚失败：%s，错误：%v", table, err)
			}
		}
	})

	t.Run("已有事实拒绝删表", func(t *testing.T) {
		db := testutil.NewPostgresTransaction(t)
		_ = prepareMigrationSchema(t, db, 166)
		executeMigration(t, db, "000165_create_public_outbox.up.sql")
		executeMigration(t, db, "000166_create_system_config.up.sql")
		if err := db.Exec(`INSERT INTO tb_system_config
			(config_key, config_value, value_type, module, description)
			VALUES ('foundation.test.fact', 'true', 'bool', 'foundation', '回滚保护测试')`).Error; err != nil {
			t.Fatalf("准备配置事实失败：%v", err)
		}
		if err := db.Transaction(func(tx *gorm.DB) error {
			return executeMigrationWithError(tx, "000166_create_system_config.down.sql")
		}); err == nil || !strings.Contains(err.Error(), "禁止删表回滚") {
			t.Fatalf("已有事实时 down 迁移必须明确拒绝：%v", err)
		}
		var count int64
		if err := db.Table("tb_system_config").Count(&count).Error; err != nil || count != 1 {
			t.Fatalf("拒绝回滚后配置事实丢失：%v，数量：%d", err, count)
		}
	})
}

func prepareMigrationSchema(t *testing.T, db *gorm.DB, version uint) string {
	t.Helper()
	schema := "foundation_test_" + strings.ReplaceAll(uuid.NewString(), "-", "")
	if err := db.Exec(fmt.Sprintf(`CREATE SCHEMA %s`, schema)).Error; err != nil {
		t.Fatalf("创建隔离迁移 schema 失败：%v", err)
	}
	if err := db.Exec(fmt.Sprintf(`SET LOCAL search_path TO %s`, schema)).Error; err != nil {
		t.Fatalf("切换隔离迁移 schema 失败：%v", err)
	}
	if err := db.Exec("CREATE TABLE schema_migrations (version bigint NOT NULL, dirty boolean NOT NULL)").Error; err != nil {
		t.Fatalf("创建隔离迁移版本表失败：%v", err)
	}
	if err := db.Exec("INSERT INTO schema_migrations (version, dirty) VALUES (?, false)", version).Error; err != nil {
		t.Fatalf("写入隔离迁移版本失败：%v", err)
	}
	return schema
}

func executeMigration(t *testing.T, db *gorm.DB, name string) {
	t.Helper()
	if err := executeMigrationWithError(db, name); err != nil {
		t.Fatalf("执行迁移 %s 失败：%v", name, err)
	}
}

func executeMigrationWithError(db *gorm.DB, name string) error {
	path := filepath.Join("..", "..", "..", "migrations", name)
	content, err := os.ReadFile(path)
	if err != nil {
		return err
	}
	for _, statement := range splitSQLStatements(string(content)) {
		if err := db.Exec(statement).Error; err != nil {
			return err
		}
	}
	return nil
}

func splitSQLStatements(content string) []string {
	statements := make([]string, 0)
	start := 0
	inSingleQuote := false
	inDoubleQuote := false
	inDollarBlock := false
	for index := 0; index < len(content); index++ {
		if index+1 < len(content) && content[index:index+2] == "$$" && !inSingleQuote && !inDoubleQuote {
			inDollarBlock = !inDollarBlock
			index++
			continue
		}
		if inDollarBlock {
			continue
		}
		switch content[index] {
		case '\'':
			if !inDoubleQuote {
				inSingleQuote = !inSingleQuote
			}
		case '"':
			if !inSingleQuote {
				inDoubleQuote = !inDoubleQuote
			}
		case ';':
			if !inSingleQuote && !inDoubleQuote {
				statement := strings.TrimSpace(content[start : index+1])
				if statement != "" {
					statements = append(statements, statement)
				}
				start = index + 1
			}
		}
	}
	if tail := strings.TrimSpace(content[start:]); tail != "" {
		statements = append(statements, tail)
	}
	return statements
}

func hasFinding(report releasegate.Report, code string) bool {
	for _, finding := range report.Findings {
		if finding.Code == code {
			return true
		}
	}
	return false
}
