package persistence

import (
	"context"
	"crypto/rand"
	"database/sql"
	"errors"
	"fmt"
	"strconv"
	"strings"
	"time"

	"github.com/nexuscrm/kernel/pkg/schema"
	"github.com/nexuscrm/kernel/pkg/schemacatalog"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionstatev1"
)

const PermissionStateVersionTable = constants.TablePermissionStateVersion

type PermissionStateEnforcementMode string

const (
	PermissionStateDarkMode     PermissionStateEnforcementMode = "dark"
	PermissionStateEnforcedMode PermissionStateEnforcementMode = "enforced"
)

func (mode PermissionStateEnforcementMode) Valid() bool {
	return mode == PermissionStateDarkMode || mode == PermissionStateEnforcedMode
}

// PermissionState is the validated durable authority generation and activation
// mode. The catalog owns its physical shape; this module owns initialization
// and readback semantics.
type PermissionState struct {
	Version         permissionstatev1.Version
	EnforcementMode PermissionStateEnforcementMode
}

func (state PermissionState) Validate() error {
	if err := state.Version.Validate(); err != nil {
		return fmt.Errorf("permission state: invalid version: %w", err)
	}
	if !state.EnforcementMode.Valid() {
		return errors.New("permission state: invalid enforcement mode")
	}
	return nil
}

type permissionStateLineageSource func() (permissionstatev1.Lineage, error)

// PermissionStateRepository is the sole durable read and initialization path
// for the Permission State singleton. Runtime callers use CheckReady, which is
// strictly read-only; only an explicit control-plane apply may call
// EnsureInitialized.
type PermissionStateRepository struct {
	db         *sql.DB
	newLineage permissionStateLineageSource
}

func NewPermissionStateRepository(db *sql.DB) (*PermissionStateRepository, error) {
	return newPermissionStateRepository(db, randomPermissionStateLineage)
}

func newPermissionStateRepository(db *sql.DB, source permissionStateLineageSource) (*PermissionStateRepository, error) {
	if db == nil {
		return nil, errors.New("permission state: database is required")
	}
	if source == nil {
		return nil, errors.New("permission state: lineage source is required")
	}
	return &PermissionStateRepository{db: db, newLineage: source}, nil
}

// EnsureInitialized creates the singleton only when an explicit apply observes
// an empty, schema-valid table. It never rewrites an existing row.
func (repository *PermissionStateRepository) EnsureInitialized(ctx context.Context) (PermissionState, error) {
	if err := repository.validateContext(ctx); err != nil {
		return PermissionState{}, err
	}
	if err := verifyPermissionStateSchema(ctx, repository.db); err != nil {
		return PermissionState{}, err
	}

	tx, cleanup, err := BeginPessimisticTx(ctx, repository.db)
	if err != nil {
		return PermissionState{}, fmt.Errorf("permission state: begin initialization: %w", err)
	}
	defer func() { _ = cleanup() }()
	defer tx.Rollback()

	state, found, err := readPermissionState(ctx, tx, true)
	if err != nil {
		return PermissionState{}, err
	}
	if found {
		if err := tx.Commit(); err != nil {
			return PermissionState{}, fmt.Errorf("permission state: commit existing singleton proof: %w", err)
		}
		return state, nil
	}

	lineage, err := repository.newLineage()
	if err != nil {
		return PermissionState{}, fmt.Errorf("permission state: generate initial lineage: %w", err)
	}
	if !lineage.Valid() {
		return PermissionState{}, errors.New("permission state: generated initial lineage is invalid")
	}
	if _, err := tx.ExecContext(ctx, insertInitialPermissionStateSQL, lineage.Bytes()); err != nil {
		return PermissionState{}, fmt.Errorf("permission state: insert initial singleton: %w", err)
	}

	state, found, err = readPermissionState(ctx, tx, true)
	if err != nil {
		return PermissionState{}, err
	}
	if !found {
		return PermissionState{}, errors.New("permission state: initial singleton readback is missing")
	}
	if err := tx.Commit(); err != nil {
		return PermissionState{}, fmt.Errorf("permission state: commit initialization: %w", err)
	}
	return state, nil
}

// CheckReady proves the exact schema and singleton value without mutating
// either. Missing state after bootstrap is a readiness failure, not a repair
// request.
func (repository *PermissionStateRepository) CheckReady(ctx context.Context) (PermissionState, error) {
	if err := repository.validateContext(ctx); err != nil {
		return PermissionState{}, err
	}
	if err := verifyPermissionStateSchema(ctx, repository.db); err != nil {
		return PermissionState{}, err
	}
	return repository.LoadCurrent(ctx)
}

// LoadCurrent performs the strong singleton read used after CheckReady has
// already proved the immutable managed schema. When ctx carries a transaction,
// the singleton joins that transaction so a caller can bind subsequent
// permission reads to the same snapshot. It does not cache or weaken the
// durable lineage/revision/mode validation.
func (repository *PermissionStateRepository) LoadCurrent(ctx context.Context) (PermissionState, error) {
	if err := repository.validateContext(ctx); err != nil {
		return PermissionState{}, err
	}
	state, found, err := readPermissionState(ctx, repository.queryer(ctx), false)
	if err != nil {
		return PermissionState{}, err
	}
	if !found {
		return PermissionState{}, errors.New("permission state: singleton is missing")
	}
	return state, nil
}

// LockCurrent returns the latest committed Permission State while holding the
// singleton lock through the caller-owned pessimistic transaction. Effect
// owners use this as their final authorization fence: a concurrent permission
// writer either commits first and changes the observed version, or waits until
// the authorized effect commits.
func (repository *PermissionStateRepository) LockCurrent(ctx context.Context) (PermissionState, error) {
	if err := repository.validateContext(ctx); err != nil {
		return PermissionState{}, err
	}
	tx, ok := ctx.Value(txContextKey{}).(*sql.Tx)
	owner, owned := ctx.Value(pessimisticTxOwnerKey{}).(*TransactionManager)
	if !ok || tx == nil || !owned || owner == nil || !owner.ownsPessimisticTx(ctx) {
		return PermissionState{}, errors.New(
			"permission state: lock requires an owned pessimistic transaction",
		)
	}
	state, found, err := readPermissionState(ctx, tx, true)
	if err != nil {
		return PermissionState{}, err
	}
	if !found {
		return PermissionState{}, errors.New("permission state: singleton is missing")
	}
	return state, nil
}

func (repository *PermissionStateRepository) queryer(ctx context.Context) permissionStateQuerier {
	if ctx != nil {
		if tx, ok := ctx.Value(txContextKey{}).(*sql.Tx); ok && tx != nil {
			return tx
		}
	}
	return repository.db
}

func (repository *PermissionStateRepository) validateContext(ctx context.Context) error {
	if repository == nil || repository.db == nil {
		return errors.New("permission state: repository is unavailable")
	}
	if ctx == nil {
		return errors.New("permission state: context is required")
	}
	return nil
}

func randomPermissionStateLineage() (permissionstatev1.Lineage, error) {
	value := make([]byte, permissionstatev1.LineageBytes)
	if _, err := rand.Read(value); err != nil {
		return permissionstatev1.Lineage{}, err
	}
	return permissionstatev1.LineageFromBytes(value)
}

type permissionStateQuerier interface {
	QueryContext(context.Context, string, ...any) (*sql.Rows, error)
}

func readPermissionState(ctx context.Context, queryer permissionStateQuerier, lock bool) (PermissionState, bool, error) {
	query := readPermissionStateSQL
	if lock {
		query = lockPermissionStateSQL
	}
	rows, err := queryer.QueryContext(ctx, query)
	if err != nil {
		return PermissionState{}, false, fmt.Errorf("permission state: read singleton: %w", err)
	}
	defer rows.Close()

	if !rows.Next() {
		if err := rows.Err(); err != nil {
			return PermissionState{}, false, fmt.Errorf("permission state: iterate singleton: %w", err)
		}
		return PermissionState{}, false, nil
	}
	state, err := scanPermissionState(rows)
	if err != nil {
		return PermissionState{}, false, err
	}
	if rows.Next() {
		return PermissionState{}, false, errors.New("permission state: multiple singleton rows")
	}
	if err := rows.Err(); err != nil {
		return PermissionState{}, false, fmt.Errorf("permission state: iterate singleton: %w", err)
	}
	return state, true, nil
}

type permissionStateScanner interface {
	Scan(...any) error
}

func scanPermissionState(scanner permissionStateScanner) (PermissionState, error) {
	var (
		singletonKey int64
		lineageBytes []byte
		revision     string
		mode         string
		created      time.Time
		modified     time.Time
	)
	if err := scanner.Scan(&singletonKey, &lineageBytes, &revision, &mode, &created, &modified); err != nil {
		return PermissionState{}, fmt.Errorf("permission state: scan singleton: %w", err)
	}
	return permissionStateFromDurableValues(
		singletonKey,
		lineageBytes,
		revision,
		mode,
		created,
		modified,
	)
}

func permissionStateFromDurableValues(
	singletonKey int64,
	lineageBytes []byte,
	revision string,
	mode string,
	created time.Time,
	modified time.Time,
) (PermissionState, error) {
	if singletonKey != 1 {
		return PermissionState{}, errors.New("permission state: singleton key must be one")
	}
	lineage, err := permissionstatev1.LineageFromBytes(lineageBytes)
	if err != nil {
		return PermissionState{}, fmt.Errorf("permission state: invalid lineage: %w", err)
	}
	revisionValue, err := strconv.ParseUint(revision, 10, 64)
	if err != nil || strconv.FormatUint(revisionValue, 10) != revision {
		return PermissionState{}, errors.New("permission state: invalid revision")
	}
	version, err := permissionstatev1.NewVersion(lineage, revisionValue)
	if err != nil {
		return PermissionState{}, fmt.Errorf("permission state: invalid version: %w", err)
	}
	state := PermissionState{
		Version:         version,
		EnforcementMode: PermissionStateEnforcementMode(mode),
	}
	if err := state.Validate(); err != nil {
		return PermissionState{}, err
	}
	if created.IsZero() || modified.IsZero() || modified.Before(created) {
		return PermissionState{}, errors.New("permission state: invalid temporal audit fields")
	}
	return state, nil
}

func verifyPermissionStateSchema(ctx context.Context, db *sql.DB) error {
	definition, err := permissionStateTableDefinition()
	if err != nil {
		return err
	}
	rows, err := db.QueryContext(ctx, inspectPermissionStateColumnsSQL, PermissionStateVersionTable)
	if err != nil {
		return fmt.Errorf("permission state: inspect schema: %w", err)
	}
	defer rows.Close()

	expected := make(map[string]schema.ColumnDefinition, len(definition.Columns))
	for _, column := range definition.Columns {
		expected[strings.ToLower(column.Name)] = column
	}
	found := make(map[string]struct{}, len(expected))
	for rows.Next() {
		var (
			name         string
			columnType   string
			nullable     string
			defaultValue sql.NullString
			extra        string
		)
		if err := rows.Scan(&name, &columnType, &nullable, &defaultValue, &extra); err != nil {
			return fmt.Errorf("permission state: scan schema: %w", err)
		}
		key := strings.ToLower(name)
		column, ok := expected[key]
		if !ok {
			return fmt.Errorf("permission state: schema has unexpected column %s", name)
		}
		if _, duplicate := found[key]; duplicate {
			return fmt.Errorf("permission state: schema repeats column %s", name)
		}
		if !compatiblePhysicalColumnType(columnType, column.Type) ||
			strings.EqualFold(nullable, "YES") != column.Nullable ||
			!permissionStateDefaultMatches(defaultValue, column.Default) ||
			!permissionStateExtraMatches(extra, column.AutoIncrement) {
			return fmt.Errorf("permission state: schema column %s is incompatible", column.Name)
		}
		found[key] = struct{}{}
	}
	if err := rows.Err(); err != nil {
		return fmt.Errorf("permission state: iterate schema: %w", err)
	}
	if len(found) != len(expected) {
		return fmt.Errorf("permission state: schema has %d columns, want %d", len(found), len(expected))
	}
	if err := VerifyExactTableIndex(ctx, db, PermissionStateVersionTable, "PRIMARY", true, []string{"SingletonKey"}); err != nil {
		return fmt.Errorf("permission state: %w", err)
	}
	return nil
}

func permissionStateTableDefinition() (schema.TableDefinition, error) {
	definitions, err := schemacatalog.ManagedTableDefinitions()
	if err != nil {
		return schema.TableDefinition{}, fmt.Errorf("permission state: load managed schema catalog: %w", err)
	}
	for _, definition := range definitions {
		if definition.TableName == PermissionStateVersionTable {
			return definition, nil
		}
	}
	return schema.TableDefinition{}, errors.New("permission state: managed schema definition is missing")
}

func permissionStateDefaultMatches(actual sql.NullString, expected string) bool {
	expected = strings.TrimSpace(expected)
	if expected == "" {
		return !actual.Valid
	}
	return actual.Valid && strings.EqualFold(strings.TrimSpace(actual.String), expected)
}

func permissionStateExtraMatches(actual string, autoIncrement bool) bool {
	actual = strings.TrimSpace(actual)
	if autoIncrement {
		return strings.EqualFold(actual, "auto_increment")
	}
	return actual == ""
}

const inspectPermissionStateColumnsSQL = `SELECT COLUMN_NAME, LOWER(COLUMN_TYPE), IS_NULLABLE, COLUMN_DEFAULT, EXTRA
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?
ORDER BY ORDINAL_POSITION`

const readPermissionStateSQL = `SELECT SingletonKey, Lineage, CAST(Revision AS CHAR), EnforcementMode, CreatedDate, LastModifiedDate
FROM _System_PermissionStateVersion
ORDER BY SingletonKey`

const lockPermissionStateSQL = readPermissionStateSQL + `
FOR UPDATE`

const insertInitialPermissionStateSQL = `INSERT INTO _System_PermissionStateVersion
	(SingletonKey, Lineage, Revision, EnforcementMode)
VALUES (1, ?, 1, 'dark')
ON DUPLICATE KEY UPDATE SingletonKey = SingletonKey`
