package catalogsystem

import (
	"context"
	"database/sql"
	"errors"
	"fmt"

	systemauthorityv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/systemlayoutbundleauthorityv1"
)

// applyCommand executes one authenticated, exact-key system-slot command.
// The writer capability is deliberately package-internal until a later gate
// owns the concrete admission path that mints authenticated principals and
// reviewed grants.
func (w *systemSlotWriter) applyCommand(
	ctx context.Context,
	commandID systemauthorityv1.CommandID,
	request systemauthorityv1.SystemLayoutBundleSlotCommandRequest,
) (commandOutcome, error) {
	// Authorization deliberately precedes grammar validation, journal lookup,
	// lock acquisition, and every other database operation. An unauthorized
	// caller therefore learns nothing about either the request or CommandID.
	if err := w.authorize(request); err != nil {
		return commandOutcome{}, err
	}
	if err := systemauthorityv1.ValidateCommandID(commandID); err != nil {
		return commandOutcome{}, err
	}
	requestDigest, err := systemauthorityv1.CommandRequestDigest(request)
	if err != nil {
		return commandOutcome{}, err
	}
	desiredValueDigest, err := desiredDigest(request)
	if err != nil {
		return commandOutcome{}, err
	}
	command := durableCommand{
		CommandID:     commandID,
		Principal:     w.principal,
		Request:       request,
		RequestDigest: requestDigest,
	}

	return w.authority.transact(ctx, func(tx *sql.Tx) (commandOutcome, error) {
		existing, err := readDurableCommand(ctx, tx, commandID)
		switch {
		case err == nil:
			if existing.Principal != command.Principal ||
				existing.Request != command.Request ||
				existing.RequestDigest != command.RequestDigest {
				return commandOutcome{}, ErrConflict
			}
			outcome, err := readDurableOutcome(ctx, tx, existing)
			if err != nil {
				return commandOutcome{}, err
			}
			return publicOutcome(outcome), nil
		case !errors.Is(err, sql.ErrNoRows):
			return commandOutcome{}, err
		}

		if err := insertDurableCommand(ctx, tx, command); err != nil {
			if isDuplicate(err) {
				return commandOutcome{}, ErrConflict
			}
			return commandOutcome{}, err
		}

		var result Result
		switch request.Operation {
		case systemauthorityv1.OperationCreate:
			result, err = applyCreateTransition(
				ctx,
				tx,
				command,
				desiredValueDigest,
			)
		case systemauthorityv1.OperationRetire:
			result, err = applyRetireTransition(
				ctx,
				tx,
				command,
				desiredValueDigest,
			)
		default:
			// ValidateCommandRequest makes this unreachable. Keep the closed
			// branch so later contract drift fails rather than becoming an
			// implicit operation.
			err = fmt.Errorf("%w: unsupported operation %q", ErrConflict, request.Operation)
		}
		if err != nil {
			return commandOutcome{}, err
		}

		outcome, err := completeOutcome(command, result)
		if err != nil {
			return commandOutcome{}, err
		}
		if err := insertDurableOutcome(ctx, tx, outcome); err != nil {
			return commandOutcome{}, err
		}
		return publicOutcome(outcome), nil
	})
}

func applyCreateTransition(
	ctx context.Context,
	tx *sql.Tx,
	command durableCommand,
	desiredValueDigest string,
) (Result, error) {
	_, present, err := lockCurrentSlot(
		ctx,
		tx,
		command.Request.TargetBundleKey,
	)
	if err != nil {
		if errors.Is(err, ErrCorrupt) {
			return ResultAuthorityStateConflict, nil
		}
		return "", err
	}
	historyPresent, err := lockAnyHistory(
		ctx,
		tx,
		command.Request.TargetBundleKey,
	)
	if err != nil {
		return "", err
	}
	if present {
		// A valid ACTIVE or TOMBSTONE current row is not ABSENT. The typed
		// value has already been validated and its digest recomputed.
		return ResultExpectedPriorMismatch, nil
	}
	if historyPresent {
		// History without current state is corrupt residue. It is not an
		// adoptable absence and permanently blocks reuse of this key.
		return ResultAuthorityStateConflict, nil
	}
	return persistTransition(ctx, tx, func() error {
		return persistCreate(ctx, tx, command, desiredValueDigest)
	})
}

func applyRetireTransition(
	ctx context.Context,
	tx *sql.Tx,
	command durableCommand,
	desiredValueDigest string,
) (Result, error) {
	current, present, err := lockCurrentSlot(
		ctx,
		tx,
		command.Request.TargetBundleKey,
	)
	if err != nil {
		if errors.Is(err, ErrCorrupt) {
			return ResultAuthorityStateConflict, nil
		}
		return "", err
	}
	if !present ||
		current.Value.State != systemauthorityv1.SlotStateActive ||
		current.Digest != command.Request.ExpectedPrior.SlotValueDigest {
		return ResultExpectedPriorMismatch, nil
	}

	history, err := lockHistory(ctx, tx, command.Request.TargetBundleKey)
	if err != nil {
		if errors.Is(err, ErrCorrupt) {
			return ResultAuthorityStateConflict, nil
		}
		return "", err
	}
	if err := validateActiveLineage(ctx, tx, current, history); err != nil {
		if errors.Is(err, ErrCorrupt) {
			return ResultAuthorityStateConflict, nil
		}
		return "", err
	}

	return persistTransition(ctx, tx, func() error {
		return persistRetire(
			ctx,
			tx,
			command,
			desiredValueDigest,
			current,
		)
	})
}

func persistTransition(
	ctx context.Context,
	tx *sql.Tx,
	persist func() error,
) (Result, error) {
	if err := beginTransition(ctx, tx); err != nil {
		return "", err
	}
	if err := persist(); err != nil {
		var result Result
		switch {
		case errors.Is(err, errExpectedPriorConflict):
			result = ResultExpectedPriorMismatch
		case errors.Is(err, errHistoryConflict):
			result = ResultAuthorityStateConflict
		default:
			return "", err
		}
		if rollbackErr := discardTransition(ctx, tx); rollbackErr != nil {
			return "", rollbackErr
		}
		if releaseErr := keepTransition(ctx, tx); releaseErr != nil {
			return "", releaseErr
		}
		return result, nil
	}
	if err := keepTransition(ctx, tx); err != nil {
		return "", err
	}
	return ResultApplied, nil
}
