// Package releasegate 提供公共基础数据库对象的发布检查门禁。
package releasegate

import (
	"context"
	stderrors "errors"
	"sort"
	"strings"

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

const (
	// PhasePre 表示迁移或发布前检查。
	PhasePre = "pre"
	// PhasePost 表示迁移后的定义与冒烟检查。
	PhasePost = "post"
)

// Severity 表示门禁检查结果级别。
type Severity string

const (
	// SeverityInfo 表示安全的计数或对象标识。
	SeverityInfo Severity = "info"
	// SeverityBlock 表示必须阻断发布的问题。
	SeverityBlock Severity = "block"
)

// Finding 是不包含业务敏感值的门禁检查结果。
type Finding struct {
	Code     string   `json:"code"`
	Severity Severity `json:"severity"`
	Object   string   `json:"object"`
	Count    int64    `json:"count"`
	Summary  string   `json:"summary"`
}

// Report 是可重复执行的公共基础门禁报告。
type Report struct {
	Phase    string    `json:"phase"`
	Findings []Finding `json:"findings"`
}

// Passed 判断报告是否允许继续发布。
func (r Report) Passed() bool {
	for _, finding := range r.Findings {
		if finding.Severity == SeverityBlock {
			return false
		}
	}
	return true
}

// Checker 使用 PostgreSQL 事实检查公共对象定义与异常数据。
type Checker struct {
	db     *gorm.DB
	schema string
}

// NewChecker 创建公共发布门禁检查器。
func NewChecker(db *gorm.DB) *Checker {
	return &Checker{db: db, schema: "public"}
}

// NewCheckerWithSchema 创建隔离 schema 的迁移验收检查器。
func NewCheckerWithSchema(db *gorm.DB, schema string) *Checker {
	if strings.TrimSpace(schema) == "" {
		schema = "public"
	}
	return &Checker{db: db, schema: schema}
}

type tableContract struct {
	name    string
	columns map[string]string
}

var publicContracts = []tableContract{
	{name: "tb_outbox_event", columns: map[string]string{
		"event_id": "character varying", "event_type": "character varying", "payload": "jsonb",
		"status": "integer", "retry_count": "integer", "next_attempt_at": "timestamp with time zone",
		"lease_owner": "character varying", "lease_expires_at": "timestamp with time zone",
	}},
	{name: "tb_system_config", columns: map[string]string{
		"config_key": "character varying", "config_value": "text", "value_type": "character varying",
		"module": "character varying", "is_readonly": "boolean", "is_sensitive": "boolean",
	}},
}

// Run 执行指定阶段检查；检查只读且可重复运行。
func (c *Checker) Run(ctx context.Context, phase string) (Report, error) {
	report := Report{Phase: phase, Findings: make([]Finding, 0)}
	if phase != PhasePre && phase != PhasePost {
		return report, stderrors.New("公共发布门禁阶段不受支持")
	}
	version, dirty, err := c.migrationVersion(ctx)
	if err != nil {
		return report, err
	}
	minimumVersion := uint(164)
	if phase == PhasePost {
		minimumVersion = 166
	}
	if dirty || version < minimumVersion {
		report.Findings = append(report.Findings, Finding{
			Code: "FOUNDATION_DEPENDENCY_VERSION", Severity: SeverityBlock,
			Object: "schema_migrations", Count: int64(version), Summary: "迁移依赖版本未满足或数据库处于脏状态",
		})
	}

	for _, contract := range publicContracts {
		exists, err := c.tableExists(ctx, contract.name)
		if err != nil {
			return report, err
		}
		if !exists {
			severity := SeverityInfo
			if phase == PhasePost {
				severity = SeverityBlock
			}
			report.Findings = append(report.Findings, Finding{
				Code: "FOUNDATION_OBJECT_MISSING", Severity: severity, Object: contract.name,
				Summary: "公共对象尚不存在",
			})
			continue
		}
		definitionFindings, err := c.checkColumns(ctx, contract)
		if err != nil {
			return report, err
		}
		report.Findings = append(report.Findings, definitionFindings...)
	}
	if phase == PhasePost {
		objectFindings, err := c.checkIndexesAndConstraints(ctx)
		if err != nil {
			return report, err
		}
		report.Findings = append(report.Findings, objectFindings...)
		if err := c.smokeWrite(ctx); err != nil {
			report.Findings = append(report.Findings, Finding{
				Code: "FOUNDATION_READ_WRITE_SMOKE_FAILED", Severity: SeverityBlock,
				Object: "tech-public-foundation", Count: 1, Summary: "公共对象读写冒烟失败",
			})
		}
	}

	anomalyFindings, err := c.checkAnomalies(ctx)
	if err != nil {
		return report, err
	}
	report.Findings = append(report.Findings, anomalyFindings...)
	sort.Slice(report.Findings, func(i, j int) bool {
		if report.Findings[i].Object == report.Findings[j].Object {
			return report.Findings[i].Code < report.Findings[j].Code
		}
		return report.Findings[i].Object < report.Findings[j].Object
	})
	return report, nil
}

func (c *Checker) migrationVersion(ctx context.Context) (uint, bool, error) {
	var result struct {
		Version uint `gorm:"column:version"`
		Dirty   bool `gorm:"column:dirty"`
	}
	err := c.db.WithContext(ctx).Raw("SELECT version, dirty FROM schema_migrations LIMIT 1").Scan(&result).Error
	return result.Version, result.Dirty, err
}

func (c *Checker) tableExists(ctx context.Context, table string) (bool, error) {
	var exists bool
	err := c.db.WithContext(ctx).Raw("SELECT to_regclass(?) IS NOT NULL", c.schema+"."+table).Scan(&exists).Error
	return exists, err
}

func (c *Checker) checkColumns(ctx context.Context, contract tableContract) ([]Finding, error) {
	var rows []struct {
		Name string `gorm:"column:column_name"`
		Type string `gorm:"column:data_type"`
	}
	err := c.db.WithContext(ctx).Raw(`
		SELECT column_name, data_type
		FROM information_schema.columns
		WHERE table_schema = ? AND table_name = ?`, c.schema, contract.name).Scan(&rows).Error
	if err != nil {
		return nil, err
	}
	actual := make(map[string]string, len(rows))
	for _, row := range rows {
		actual[row.Name] = row.Type
	}
	findings := make([]Finding, 0)
	for column, expectedType := range contract.columns {
		actualType, exists := actual[column]
		if !exists || !strings.EqualFold(actualType, expectedType) {
			findings = append(findings, Finding{
				Code: "FOUNDATION_DEFINITION_MISMATCH", Severity: SeverityBlock,
				Object: contract.name + "." + column, Count: 1, Summary: "公共对象字段定义不符合契约",
			})
		}
	}
	return findings, nil
}

func (c *Checker) checkAnomalies(ctx context.Context) ([]Finding, error) {
	checks := []struct {
		table   string
		code    string
		summary string
		query   string
	}{
		{"tb_outbox_event", "OUTBOX_DUPLICATE_EVENT_ID", "存在重复事件 ID", `SELECT COUNT(*) FROM (SELECT event_id FROM tb_outbox_event GROUP BY event_id HAVING COUNT(*) > 1) AS conflicts`},
		{"tb_outbox_event", "OUTBOX_INVALID_REQUIRED_FIELD", "存在必填字段空值", `SELECT COUNT(*) FROM tb_outbox_event WHERE event_id IS NULL OR event_type IS NULL OR payload IS NULL`},
		{"tb_outbox_event", "OUTBOX_INVALID_STATUS", "存在非法投递状态", `SELECT COUNT(*) FROM tb_outbox_event WHERE status NOT IN (1,2,3,4)`},
		{"tb_outbox_event", "OUTBOX_UNDELIVERED", "存在未投递事件", `SELECT COUNT(*) FROM tb_outbox_event WHERE status IN (1,2,4)`},
		{"tb_outbox_event", "OUTBOX_EXPIRED_LEASE", "存在过期租约", `SELECT COUNT(*) FROM tb_outbox_event WHERE status = 2 AND lease_expires_at < NOW()`},
		{"tb_system_config", "SYSTEM_CONFIG_DUPLICATE_KEY", "存在重复配置 Key", `SELECT COUNT(*) FROM (SELECT config_key FROM tb_system_config GROUP BY config_key HAVING COUNT(*) > 1) AS conflicts`},
		{"tb_system_config", "SYSTEM_CONFIG_INVALID_REQUIRED_FIELD", "存在配置必填字段空值", `SELECT COUNT(*) FROM tb_system_config WHERE config_key IS NULL OR config_value IS NULL OR value_type IS NULL OR module IS NULL`},
		{"tb_system_config", "SYSTEM_CONFIG_INVALID_TYPE", "存在非法配置类型", `SELECT COUNT(*) FROM tb_system_config WHERE value_type NOT IN ('string','int','bool','json')`},
	}
	findings := make([]Finding, 0, len(checks))
	for _, check := range checks {
		exists, err := c.tableExists(ctx, check.table)
		if err != nil {
			return nil, err
		}
		if !exists {
			continue
		}
		var count int64
		if err := c.db.WithContext(ctx).Raw(check.query).Scan(&count).Error; err != nil {
			return nil, err
		}
		if count > 0 {
			findings = append(findings, Finding{
				Code: check.code, Severity: SeverityBlock, Object: check.table,
				Count: count, Summary: check.summary,
			})
		}
	}
	for _, table := range []string{"tb_export_task", "tb_iot_card_import_task", "tb_device_import_task", "tb_order_package_invalidate_task"} {
		exists, err := c.tableExists(ctx, table)
		if err != nil {
			return nil, err
		}
		if !exists {
			continue
		}
		var count int64
		if err := c.db.WithContext(ctx).Table(table).Where("status IN ?", []int{1, 2}).Count(&count).Error; err != nil {
			return nil, err
		}
		if count > 0 {
			findings = append(findings, Finding{
				Code: "ASYNC_TASK_UNFINISHED", Severity: SeverityBlock, Object: table,
				Count: count, Summary: "存在待处理或处理中任务",
			})
		}
	}
	return findings, nil
}

func (c *Checker) checkIndexesAndConstraints(ctx context.Context) ([]Finding, error) {
	expectedConstraints := map[string][]string{
		"tb_outbox_event":  {"uq_outbox_event_id", "ck_outbox_event_status", "ck_outbox_event_payload_version", "ck_outbox_event_retry_count"},
		"tb_system_config": {"uq_system_config_key", "ck_system_config_value_type"},
	}
	expectedIndexes := map[string][]string{
		"tb_outbox_event":  {"idx_outbox_event_claim", "idx_outbox_event_type_status", "idx_outbox_event_aggregate", "idx_outbox_event_resource", "idx_outbox_event_request_id", "idx_outbox_event_correlation_id"},
		"tb_system_config": {"idx_system_config_module_key"},
	}
	findings := make([]Finding, 0)
	for table, names := range expectedConstraints {
		for _, name := range names {
			var count int64
			if err := c.db.WithContext(ctx).Raw(`SELECT COUNT(*) FROM information_schema.table_constraints
				WHERE table_schema = ? AND table_name = ? AND constraint_name = ?`, c.schema, table, name).Scan(&count).Error; err != nil {
				return nil, err
			}
			if count != 1 {
				findings = append(findings, Finding{Code: "FOUNDATION_CONSTRAINT_MISSING", Severity: SeverityBlock, Object: table + "." + name, Count: count, Summary: "公共约束缺失或重复"})
			}
		}
	}
	for table, names := range expectedIndexes {
		for _, name := range names {
			var count int64
			if err := c.db.WithContext(ctx).Raw(`SELECT COUNT(*) FROM pg_indexes
				WHERE schemaname = ? AND tablename = ? AND indexname = ?`, c.schema, table, name).Scan(&count).Error; err != nil {
				return nil, err
			}
			if count != 1 {
				findings = append(findings, Finding{Code: "FOUNDATION_INDEX_MISSING", Severity: SeverityBlock, Object: table + "." + name, Count: count, Summary: "公共索引缺失或重复"})
			}
		}
	}
	return findings, nil
}

var errSmokeRollback = stderrors.New("公共对象冒烟回滚")

func (c *Checker) smokeWrite(ctx context.Context) error {
	err := c.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		eventID := "smoke-" + uuid.NewString()
		if err := tx.Exec(`INSERT INTO tb_outbox_event
			(event_id, event_type, payload_version, aggregate_type, aggregate_id, resource_type, resource_id, payload)
			VALUES (?, 'foundation.smoke', 1, 'foundation', 'smoke', 'foundation', 'smoke', '{}'::jsonb)`, eventID).Error; err != nil {
			return err
		}
		configKey := "foundation.smoke." + uuid.NewString()
		if err := tx.Exec(`INSERT INTO tb_system_config
			(config_key, config_value, value_type, module, description)
			VALUES (?, 'true', 'bool', 'foundation', '公共对象读写冒烟')`, configKey).Error; err != nil {
			return err
		}
		return errSmokeRollback
	})
	if stderrors.Is(err, errSmokeRollback) {
		return nil
	}
	return err
}
