package persistence

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

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

// PermissionStateTransactionEffect exposes the shared Permission State
// advance to a transaction owner outside TransactionManager. Apply never
// commits or rolls back tx. Prove performs a fresh read and accepts a later
// same-lineage revision because another valid writer may have advanced it.
type PermissionStateTransactionEffect struct{}

func NewPermissionStateTransactionEffect() PermissionStateTransactionEffect {
	return PermissionStateTransactionEffect{}
}

func (PermissionStateTransactionEffect) Apply(
	ctx context.Context,
	tx *sql.Tx,
) (permissionstatev1.AdvanceReceipt, error) {
	if ctx == nil {
		return permissionstatev1.AdvanceReceipt{}, errors.New(
			"permission state transaction effect: context is required",
		)
	}
	if tx == nil {
		return permissionstatev1.AdvanceReceipt{}, errors.New(
			"permission state transaction effect: transaction is required",
		)
	}
	return advancePermissionStateOnTransaction(ctx, tx)
}

func (PermissionStateTransactionEffect) Prove(
	ctx context.Context,
	db *sql.DB,
	receipt permissionstatev1.AdvanceReceipt,
) (bool, error) {
	if ctx == nil {
		return false, errors.New("permission state transaction effect: context is required")
	}
	if db == nil {
		return false, errors.New("permission state transaction effect: database is required")
	}
	if _, err := receipt.MarshalBinary(); err != nil {
		return false, fmt.Errorf(
			"permission state transaction effect: invalid receipt: %w",
			err,
		)
	}

	state, found, err := readPermissionState(ctx, db, false)
	if err != nil {
		return false, err
	}
	if !found {
		return false, errors.New("permission state transaction effect: singleton is missing")
	}
	after := receipt.AfterVersion()
	if !bytes.Equal(state.Version.Lineage().Bytes(), after.Lineage().Bytes()) {
		return false, nil
	}
	return state.Version.Revision() >= after.Revision(), nil
}

func advancePermissionStateOnTransaction(
	ctx context.Context,
	tx *sql.Tx,
) (permissionstatev1.AdvanceReceipt, error) {
	state, found, err := readPermissionState(ctx, tx, true)
	if err != nil {
		return permissionstatev1.AdvanceReceipt{}, err
	}
	if !found {
		return permissionstatev1.AdvanceReceipt{}, errors.New(
			"permission state authority: singleton is missing",
		)
	}
	before := state.Version
	if before.Revision() == math.MaxUint64 {
		return permissionstatev1.AdvanceReceipt{}, errors.New(
			"permission state authority: revision overflow",
		)
	}
	after, err := permissionstatev1.NewVersion(before.Lineage(), before.Revision()+1)
	if err != nil {
		return permissionstatev1.AdvanceReceipt{}, fmt.Errorf(
			"permission state authority: construct next version: %w",
			err,
		)
	}
	receipt, err := permissionstatev1.NewAdvanceReceipt(before, after)
	if err != nil {
		return permissionstatev1.AdvanceReceipt{}, fmt.Errorf(
			"permission state authority: construct advance receipt: %w",
			err,
		)
	}
	result, err := tx.ExecContext(
		ctx,
		advancePermissionStateSQL,
		before.Lineage().Bytes(),
		strconv.FormatUint(before.Revision(), 10),
		string(state.EnforcementMode),
	)
	if err != nil {
		return permissionstatev1.AdvanceReceipt{}, fmt.Errorf(
			"permission state authority: advance revision: %w",
			err,
		)
	}
	rows, err := result.RowsAffected()
	if err != nil {
		return permissionstatev1.AdvanceReceipt{}, fmt.Errorf(
			"permission state authority: inspect advance result: %w",
			err,
		)
	}
	if rows != 1 {
		return permissionstatev1.AdvanceReceipt{}, fmt.Errorf(
			"permission state authority: advance affected %d rows, want one",
			rows,
		)
	}
	return receipt, nil
}

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