package persistence

import (
	"bytes"
	"context"
	"database/sql"
	"errors"
	"fmt"
	"strconv"
	"time"

	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionstatev1"
)

// ErrPermissionStateRestoreIndeterminate means a finalizer commit failed and a
// fresh strong read could not prove the exact requested lineage/revision/mode.
// Traffic and writers must remain externally fenced until an exact retry or
// operator proof resolves the outcome.
var ErrPermissionStateRestoreIndeterminate = errors.New("permission state restore outcome is indeterminate")

const permissionStateRestoreProofTimeout = 10 * time.Second

// PermissionStateRestoreFinalizer owns the exceptional lineage replacement
// used after database restore, recreate, or rewind. It is deliberately separate
// from PermissionStateRepository readiness and initialization; it is not an
// ordinary repair path.
type PermissionStateRestoreFinalizer struct {
	db *sql.DB
}

// NewPermissionStateRestoreFinalizer constructs the exceptional restore-only
// finalizer over the database whose restored Permission State it will replace.
func NewPermissionStateRestoreFinalizer(db *sql.DB) (*PermissionStateRestoreFinalizer, error) {
	if db == nil {
		return nil, errors.New("permission state restore: database is required")
	}
	return &PermissionStateRestoreFinalizer{db: db}, nil
}

// FinalizeExternallyFencedRestore replaces the exact expected restored
// Permission State lineage with a caller-supplied fresh lineage at revision one
// in dark mode.
//
// The caller must keep traffic, writers, background work, and long-lived
// transports externally fenced for the entire operation and any indeterminate
// outcome. Both lineages must come from the externally held restore plan. The
// fresh lineage must be generated outside the restored database and must never
// have been used before. The expected predecessor prevents a stale finalizer
// request from replacing a later lineage. An exact retry is idempotent; the
// fresh lineage at any other current state is rejected rather than rewound.
func (finalizer *PermissionStateRestoreFinalizer) FinalizeExternallyFencedRestore(
	ctx context.Context,
	expectedRestoredLineage permissionstatev1.Lineage,
	freshLineage permissionstatev1.Lineage,
) (PermissionState, error) {
	if finalizer == nil || finalizer.db == nil {
		return PermissionState{}, errors.New("permission state restore: finalizer is unavailable")
	}
	if ctx == nil {
		return PermissionState{}, errors.New("permission state restore: context is required")
	}
	if !expectedRestoredLineage.Valid() {
		return PermissionState{}, errors.New("permission state restore: expected restored lineage is invalid")
	}
	if !freshLineage.Valid() {
		return PermissionState{}, errors.New("permission state restore: fresh lineage is invalid")
	}
	if bytes.Equal(expectedRestoredLineage.Bytes(), freshLineage.Bytes()) {
		return PermissionState{}, errors.New("permission state restore: fresh lineage must differ from restored lineage")
	}
	if err := verifyPermissionStateSchema(ctx, finalizer.db); err != nil {
		return PermissionState{}, err
	}

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

	inspection, err := inspectPermissionStateRestoreRows(
		ctx,
		tx,
		expectedRestoredLineage,
		freshLineage,
	)
	if err != nil {
		return PermissionState{}, err
	}
	var expected PermissionState
	if inspection.exactFresh {
		expected, err = newRestoredPermissionState(freshLineage)
		if err != nil {
			return PermissionState{}, err
		}
	} else {
		if inspection.freshPresent {
			return PermissionState{}, errors.New("permission state restore: fresh lineage was already consumed")
		}
		if !inspection.exactRestoredPredecessor {
			return PermissionState{}, errors.New(
				"permission state restore: current lineage does not match the expected restored predecessor",
			)
		}
		if _, err := tx.ExecContext(ctx, deletePermissionStateForRestoreSQL); err != nil {
			return PermissionState{}, fmt.Errorf("permission state restore: clear restored state: %w", err)
		}
		if _, err := tx.ExecContext(ctx, insertRestoredPermissionStateSQL, freshLineage.Bytes()); err != nil {
			return PermissionState{}, fmt.Errorf("permission state restore: install fresh lineage: %w", err)
		}
		state, found, err := readPermissionState(ctx, tx, true)
		if err != nil {
			return PermissionState{}, err
		}
		if !found || !permissionStateMatchesRestore(state, freshLineage) {
			return PermissionState{}, errors.New("permission state restore: exact finalizer readback failed")
		}
		expected = state
	}

	commitErr := tx.Commit()
	cleanupErr := cleanup()
	settled = true
	if commitErr == nil {
		if cleanupErr != nil {
			return PermissionState{}, fmt.Errorf("permission state restore: committed but session cleanup failed: %w", cleanupErr)
		}
		return expected, nil
	}

	proofCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), permissionStateRestoreProofTimeout)
	defer cancel()
	provedState, proved, proofErr := finalizer.proveExact(proofCtx, freshLineage)
	if proved {
		if cleanupErr != nil {
			return PermissionState{}, fmt.Errorf("permission state restore: commit proved but session cleanup failed: %w", cleanupErr)
		}
		return provedState, nil
	}
	if proofErr == nil {
		proofErr = errors.New("permission state restore: exact committed state was not observed")
	}
	return PermissionState{}, errors.Join(
		ErrPermissionStateRestoreIndeterminate,
		fmt.Errorf("permission state restore: commit response failed: %w", commitErr),
		proofErr,
		cleanupErr,
	)
}

func (finalizer *PermissionStateRestoreFinalizer) proveExact(
	ctx context.Context,
	freshLineage permissionstatev1.Lineage,
) (PermissionState, bool, error) {
	if err := verifyPermissionStateSchema(ctx, finalizer.db); err != nil {
		return PermissionState{}, false, err
	}
	state, found, err := readPermissionState(ctx, finalizer.db, false)
	if err != nil {
		return PermissionState{}, false, err
	}
	if !found || !permissionStateMatchesRestore(state, freshLineage) {
		return PermissionState{}, false, nil
	}
	return state, true, nil
}

type permissionStateRestoreInspection struct {
	exactFresh               bool
	freshPresent             bool
	exactRestoredPredecessor bool
}

func inspectPermissionStateRestoreRows(
	ctx context.Context,
	tx *sql.Tx,
	expectedRestoredLineage permissionstatev1.Lineage,
	freshLineage permissionstatev1.Lineage,
) (permissionStateRestoreInspection, error) {
	rows, err := tx.QueryContext(ctx, lockPermissionStateSQL)
	if err != nil {
		return permissionStateRestoreInspection{}, fmt.Errorf("permission state restore: lock restored state: %w", err)
	}
	defer rows.Close()

	rowCount := 0
	exactFreshRow := false
	exactRestoredRow := false
	freshBytes := freshLineage.Bytes()
	restoredBytes := expectedRestoredLineage.Bytes()
	inspection := permissionStateRestoreInspection{}
	for rows.Next() {
		var (
			singletonKey int64
			lineageBytes []byte
			revision     string
			mode         string
			created      time.Time
			modified     time.Time
		)
		if err := rows.Scan(&singletonKey, &lineageBytes, &revision, &mode, &created, &modified); err != nil {
			return permissionStateRestoreInspection{}, fmt.Errorf("permission state restore: scan restored state: %w", err)
		}
		rowCount++
		revisionValue, revisionErr := strconv.ParseUint(revision, 10, 64)
		rowValid := singletonKey == 1 &&
			revisionErr == nil &&
			revisionValue > 0 &&
			strconv.FormatUint(revisionValue, 10) == revision &&
			PermissionStateEnforcementMode(mode).Valid() &&
			!created.IsZero() &&
			!modified.IsZero() &&
			!modified.Before(created)
		if bytes.Equal(lineageBytes, freshBytes) {
			inspection.freshPresent = true
			exactFreshRow = rowValid &&
				revisionValue == 1 &&
				mode == string(PermissionStateDarkMode)
		}
		if bytes.Equal(lineageBytes, restoredBytes) {
			exactRestoredRow = rowValid
		}
	}
	if err := rows.Err(); err != nil {
		return permissionStateRestoreInspection{}, fmt.Errorf("permission state restore: iterate restored state: %w", err)
	}
	inspection.exactFresh = rowCount == 1 && exactFreshRow
	inspection.exactRestoredPredecessor = rowCount == 1 && exactRestoredRow
	return inspection, nil
}

func newRestoredPermissionState(lineage permissionstatev1.Lineage) (PermissionState, error) {
	version, err := permissionstatev1.NewVersion(lineage, 1)
	if err != nil {
		return PermissionState{}, fmt.Errorf("permission state restore: construct expected version: %w", err)
	}
	return PermissionState{Version: version, EnforcementMode: PermissionStateDarkMode}, nil
}

func permissionStateMatchesRestore(state PermissionState, lineage permissionstatev1.Lineage) bool {
	return bytes.Equal(state.Version.Lineage().Bytes(), lineage.Bytes()) &&
		state.Version.Revision() == 1 &&
		state.EnforcementMode == PermissionStateDarkMode
}

const deletePermissionStateForRestoreSQL = `DELETE FROM _System_PermissionStateVersion`

const insertRestoredPermissionStateSQL = `INSERT INTO _System_PermissionStateVersion
	(SingletonKey, Lineage, Revision, EnforcementMode)
VALUES (1, ?, 1, 'dark')`
