package persistence

import (
	"context"
	"errors"
	"fmt"
	"math"
	"strconv"
	"time"

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

var (
	// ErrPermissionStateActivationConflict means the current durable state is
	// not the exact dark predecessor (or exact enforced retry result) selected
	// by the caller. The caller must re-read and restart its external fence.
	ErrPermissionStateActivationConflict = errors.New("permission state activation conflicts with current state")
	// ErrPermissionStateActivationIndeterminate means the activation commit
	// response failed and a fresh strong read could not prove the exact result.
	// External reader and writer fences must remain closed.
	ErrPermissionStateActivationIndeterminate = errors.New("permission state activation outcome is indeterminate")
)

const permissionStateActivationProofTimeout = 10 * time.Second

// PromoteToEnforced performs the sole ordinary dark-to-enforced transition.
// The expected version is a compare-and-set predecessor: a successful
// transition preserves its lineage, advances exactly once, and has no inverse.
// Retrying the exact request is idempotent only while the exact successor is
// still current.
func (repository *PermissionStateRepository) PromoteToEnforced(
	ctx context.Context,
	expected permissionstatev1.Version,
) (PermissionState, error) {
	if err := repository.validateContext(ctx); err != nil {
		return PermissionState{}, err
	}
	if err := expected.Validate(); err != nil {
		return PermissionState{}, errors.New("permission state activation: expected version is invalid")
	}
	if expected.Revision() == math.MaxUint64 {
		return PermissionState{}, errors.New("permission state activation: revision overflow")
	}
	expectedSuccessorVersion, err := permissionstatev1.NewVersion(
		expected.Lineage(),
		expected.Revision()+1,
	)
	if err != nil {
		return PermissionState{}, errors.New("permission state activation: successor version is invalid")
	}
	expectedSuccessor := PermissionState{
		Version:         expectedSuccessorVersion,
		EnforcementMode: PermissionStateEnforcedMode,
	}
	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 activation: begin: %w", err)
	}
	settled := false
	defer func() {
		if !settled {
			_ = tx.Rollback()
			_ = cleanup()
		}
	}()

	current, found, err := readPermissionState(ctx, tx, true)
	if err != nil {
		return PermissionState{}, err
	}
	if !found {
		return PermissionState{}, errors.Join(
			ErrPermissionStateActivationConflict,
			errors.New("permission state activation: singleton is missing"),
		)
	}
	exactRetry := permissionStateEqual(current, expectedSuccessor)
	if !exactRetry {
		comparison, compareErr := current.Version.Compare(expected)
		if compareErr != nil || comparison != 0 ||
			current.EnforcementMode != PermissionStateDarkMode {
			return PermissionState{}, errors.Join(
				ErrPermissionStateActivationConflict,
				errors.New("permission state activation: current state is not the expected dark predecessor"),
			)
		}
		result, execErr := tx.ExecContext(
			ctx,
			promotePermissionStateToEnforcedSQL,
			expected.Lineage().Bytes(),
			strconv.FormatUint(expected.Revision(), 10),
		)
		if execErr != nil {
			return PermissionState{}, fmt.Errorf("permission state activation: compare and set: %w", execErr)
		}
		affected, rowsErr := result.RowsAffected()
		if rowsErr != nil {
			return PermissionState{}, fmt.Errorf("permission state activation: affected rows: %w", rowsErr)
		}
		if affected != 1 {
			return PermissionState{}, errors.Join(
				ErrPermissionStateActivationConflict,
				fmt.Errorf("permission state activation: affected %d rows, want one", affected),
			)
		}
		current, found, err = readPermissionState(ctx, tx, true)
		if err != nil {
			return PermissionState{}, err
		}
		if !found || !permissionStateEqual(current, expectedSuccessor) {
			return PermissionState{}, errors.New("permission state activation: exact readback failed")
		}
	}

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

	proofCtx, cancel := context.WithTimeout(
		context.WithoutCancel(ctx),
		permissionStateActivationProofTimeout,
	)
	defer cancel()
	provedState, proved, proofErr := repository.proveExactActivation(
		proofCtx,
		expectedSuccessor,
	)
	if proved {
		if cleanupErr != nil {
			return PermissionState{}, fmt.Errorf(
				"permission state activation: commit proved but session cleanup failed: %w",
				cleanupErr,
			)
		}
		return provedState, nil
	}
	if proofErr == nil {
		proofErr = errors.New("permission state activation: exact committed state was not observed")
	}
	return PermissionState{}, errors.Join(
		ErrPermissionStateActivationIndeterminate,
		fmt.Errorf("permission state activation: commit response failed: %w", commitErr),
		proofErr,
		cleanupErr,
	)
}

func (repository *PermissionStateRepository) proveExactActivation(
	ctx context.Context,
	expected PermissionState,
) (PermissionState, bool, error) {
	if err := verifyPermissionStateSchema(ctx, repository.db); err != nil {
		return PermissionState{}, false, err
	}
	state, found, err := readPermissionState(ctx, repository.db, false)
	if err != nil {
		return PermissionState{}, false, err
	}
	if !found || !permissionStateEqual(state, expected) {
		return PermissionState{}, false, nil
	}
	return state, true, nil
}

func permissionStateEqual(left, right PermissionState) bool {
	comparison, err := left.Version.Compare(right.Version)
	return err == nil && comparison == 0 &&
		left.EnforcementMode == right.EnforcementMode
}

const promotePermissionStateToEnforcedSQL = `UPDATE _System_PermissionStateVersion
SET Revision = Revision + 1,
	EnforcementMode = 'enforced',
	LastModifiedDate = CURRENT_TIMESTAMP(6)
WHERE SingletonKey = 1
	AND Lineage = ?
	AND Revision = ?
	AND EnforcementMode = 'dark'`
