package controlplane

import (
	"context"
	"database/sql"
	"fmt"
	"strings"
	"time"

	"github.com/pingcap-inc/nexuscrm-controlplane/packs"
)

type applyAllSourceRevisionAdvanceAttemptState struct {
	identity *applyAllSourceRevisionAdvanceAttemptIdentity
	database *sql.DB
	advance  normalizedApplyAllSourceRevisionAdvance
	target   *packs.SourceBackedPack
	facts    packs.SourceBackedExecutionFacts
}

// failApplyAllSourceRevisionAdvanceAttempt records a failed admitted advance
// or retry against only the fresh installing incarnation minted by Begin.
func failApplyAllSourceRevisionAdvanceAttempt(
	ctx context.Context,
	attempt applyAllSourceRevisionAdvanceAttempt,
	applyErr error,
) error {
	state, err := requireApplyAllSourceRevisionAdvanceAttempt(attempt, "fail")
	if err != nil {
		return err
	}
	if applyErr == nil {
		return applyAllSourceRevisionAdvanceAttemptError(
			"fail", state.advance, fmt.Errorf("apply error is required"),
		)
	}

	row := state.identity.installingRow
	category := state.facts.Category
	predicate, predicateArgs := applyAllSourceRevisionAdvanceExactInstalledRowPredicate(row, true)
	now := time.Now().UTC()
	args := []any{installedPackStatusFailed, applyAllSourceRevisionAdvanceFailureMessage(applyErr), now}
	args = append(args, predicateArgs...)

	err = withApplyAllSourceRevisionAdvanceExactIncarnation(
		ctx, state.database, row, category,
		func(exec applyAllSourceRevisionAdvanceExactIncarnationExec, queueGuard string) error {
			update := `UPDATE _System_InstalledPack p
   SET Status = ?,
       LastError = ?,
       LastModifiedDate = ?,
       IsDeleted = 0
 WHERE ` + predicate + queueGuard
			result, err := exec(ctx, update, args...)
			if err != nil {
				return fmt.Errorf("write failed installation incarnation: %w", err)
			}
			affected, err := result.RowsAffected()
			if err != nil {
				return fmt.Errorf("failed installation incarnation rows affected: %w", err)
			}
			if affected != 1 {
				return fmt.Errorf(
					"failed installation incarnation affected %d rows; exact installing row changed",
					affected,
				)
			}
			if category == packs.ConnectorPack {
				if err := softDeleteFailedConnectorPackRuntimeRowsWithExec(
					ctx, exec, row.Name,
				); err != nil {
					return fmt.Errorf("soft-delete failed connector runtime rows: %w", err)
				}
			}
			return nil
		},
	)
	if err != nil {
		return applyAllSourceRevisionAdvanceAttemptError("fail", state.advance, err)
	}
	if category == packs.ConnectorPack {
		if err := reconcileRegisteredConnectorInboundWorkers(ctx, state.database); err != nil {
			return applyAllSourceRevisionAdvanceAttemptError(
				"fail", state.advance,
				fmt.Errorf("reconcile connector inbound workers after committed failure: %w", err),
			)
		}
	}
	return nil
}

func requireApplyAllSourceRevisionAdvanceAttempt(
	attempt applyAllSourceRevisionAdvanceAttempt,
	operation string,
) (applyAllSourceRevisionAdvanceAttemptState, error) {
	identity := attempt.identity
	advance := normalizedApplyAllSourceRevisionAdvance{index: -1}
	if identity != nil && identity.admission.identity != nil &&
		identity.selectedProofIndex >= 0 &&
		identity.selectedProofIndex < len(identity.admission.identity.proof.entries) {
		advance = identity.admission.identity.proof.entries[identity.selectedProofIndex].current.advance
	}
	fail := func(err error) (applyAllSourceRevisionAdvanceAttemptState, error) {
		return applyAllSourceRevisionAdvanceAttemptState{},
			applyAllSourceRevisionAdvanceAttemptError(operation, advance, err)
	}
	if err := validateApplyAllSourceRevisionAdvanceAttemptIdentity(identity, true); err != nil {
		return fail(err)
	}
	if identity.selectedAtomClaim == nil ||
		identity.selectedAtomClaim != attempt.witness.selectedAtomClaim {
		return fail(fmt.Errorf("attempt selected-atom claim changed from its outer witness"))
	}
	retained := identity.admission.identity
	entry := retained.proof.entries[identity.selectedProofIndex]
	if entry.current.pending == nil ||
		(entry.current.pending.Kind != ApplyAllSourceRevisionAdvancePendingAdvance &&
			entry.current.pending.Kind != ApplyAllSourceRevisionAdvancePendingRetry) {
		return fail(fmt.Errorf("attempt was not admitted as advance or retry"))
	}
	if entry.installedCompatibility == nil || entry.currentTarget == nil || entry.atom == nil {
		return fail(fmt.Errorf("attempt target and execution atom are required"))
	}
	if entry.name != strings.ToLower(strings.TrimSpace(advance.Name)) ||
		entry.current.row.Name != advance.Name {
		return fail(fmt.Errorf("attempt does not belong to the admitted pack"))
	}
	if err := validateApplyAllSourceRevisionAdvanceFrozenCurrentTargetEntry(
		entry.current, entry.currentTarget,
	); err != nil {
		return fail(fmt.Errorf("validate admitted current target: %w", err))
	}
	if err := validateApplyAllSourceRevisionAdvanceTarget(
		advance, entry.installedCompatibility,
	); err != nil {
		return fail(fmt.Errorf("validate admitted installed target: %w", err))
	}
	if err := packs.ValidateSourceBackedExecutionAtomIdentityTarget(
		identity.provenance.admissionProvenance.entries[identity.selectedProofIndex].atomIdentity,
		entry.atom, entry.installedCompatibility,
	); err != nil {
		return fail(fmt.Errorf("validate attempt execution atom identity: %w", err))
	}
	facts, err := entry.atom.Facts()
	if err != nil {
		return fail(fmt.Errorf("read attempt execution facts: %w", err))
	}

	row := identity.installingRow
	oldRow := entry.current.row
	if strings.TrimSpace(row.ID) == "" || strings.TrimSpace(row.PackInstallID) == "" ||
		row.ID != oldRow.ID || row.Name != oldRow.Name || row.Category != oldRow.Category ||
		row.PackInstallID == oldRow.PackInstallID {
		return fail(fmt.Errorf("attempt does not retain one fresh installed-row incarnation"))
	}
	if row.Status != installedPackStatusInstalling || row.IsDeleted != 0 {
		return fail(fmt.Errorf("attempt row is not the live installing incarnation"))
	}
	if facts.Name != row.Name || string(facts.Category) != row.Category ||
		(facts.Category != packs.AppPack && facts.Category != packs.ConnectorPack) {
		return fail(fmt.Errorf("attempt execution facts do not match the installing row"))
	}
	meta := facts.Metadata
	if row.PackVersion != nullString(meta.PackVersion, true) ||
		row.ManifestFormatVersion != nullString(meta.ManifestFormatVersion, true) ||
		row.PlatformCompatibility != nullString(meta.PlatformCompatibility, meta.PlatformCompatibilityValid) ||
		row.DesiredPayloadHash != nullString(facts.PayloadHash, true) ||
		row.PayloadHashAlgorithm != nullString(meta.PayloadHashAlgorithm, true) ||
		row.SourceKind != nullString(meta.SourceKind, meta.SourceKindValid) ||
		row.SourceURI != nullString(meta.SourceURI, meta.SourceURIValid) ||
		row.SourceRevision != nullString(meta.SourceRevision, meta.SourceRevisionValid) ||
		row.HostRequirementsDigest != nullString(facts.HostRequirementsDigest, true) {
		return fail(fmt.Errorf("attempt installing row does not match the admitted target facts"))
	}
	switch facts.Category {
	case packs.AppPack:
		if row.AppliedPayloadHash != oldRow.AppliedPayloadHash {
			return fail(fmt.Errorf("AppPack attempt changed the prior applied payload hash"))
		}
	case packs.ConnectorPack:
		if row.AppliedPayloadHash != nullString("", false) {
			return fail(fmt.Errorf("ConnectorPack attempt retained an applied payload hash"))
		}
	}
	return applyAllSourceRevisionAdvanceAttemptState{
		identity: identity,
		database: retained.provenance.activeRuntimeDatabase,
		advance:  advance,
		target:   entry.installedCompatibility,
		facts:    facts,
	}, nil
}

// validateApplyAllSourceRevisionAdvanceAttemptIdentity verifies the complete
// retained admission against the attempt's third detached witness, then binds
// the exact pending selection, runtime authority, options, registrations, and
// fresh installing row. It performs no database or source reads and no effects.
func validateApplyAllSourceRevisionAdvanceAttemptIdentity(
	identity *applyAllSourceRevisionAdvanceAttemptIdentity,
	requireMinted bool,
) error {
	if identity == nil || identity.admission.identity == nil {
		return fmt.Errorf("attempt is not an admission-bound capability")
	}
	provenance := identity.provenance
	if identity.admission.identity != provenance.admissionIdentity {
		return fmt.Errorf("attempt admission identity does not match its provenance")
	}
	if identity.minted != requireMinted || provenance.minted != requireMinted {
		if requireMinted {
			return fmt.Errorf("attempt was not minted after its exact begin transition")
		}
		return fmt.Errorf("attempt was minted before its exact begin transition")
	}
	if err := validateApplyAllSourceRevisionAdvanceExecutionAdmission(identity.admission); err != nil {
		return fmt.Errorf("validate retained execution admission: %w", err)
	}
	witnessed := identity.admission.identity.proof
	witnessed.provenance = provenance.admissionProvenance
	if err := validateApplyAllSourceRevisionAdvanceExecutionProof(witnessed); err != nil {
		return fmt.Errorf("validate attempt admission provenance: %w", err)
	}
	proof := identity.admission.identity.proof
	if len(proof.provenance.entries) != len(provenance.admissionProvenance.entries) ||
		len(identity.admission.identity.provenance.entries) != len(provenance.admissionProvenance.entries) {
		return fmt.Errorf("attempt identity-witness cardinality changed")
	}
	for index, witness := range provenance.admissionProvenance.entries {
		inner := proof.provenance.entries[index]
		outer := identity.admission.identity.provenance.entries[index]
		if inner.currentTargetIdentity != witness.currentTargetIdentity ||
			outer.currentTargetIdentity != witness.currentTargetIdentity ||
			inner.atomIdentity != witness.atomIdentity || outer.atomIdentity != witness.atomIdentity {
			return fmt.Errorf("attempt proof-minted identity changed at entry %d", index)
		}
	}
	runtime := proof.executionRuntime
	if runtime == nil || runtime != proof.provenance.executionRuntime ||
		runtime != identity.admission.identity.provenance.executionRuntime ||
		runtime != provenance.executionRuntime ||
		runtime.provenance == nil || runtime.provenance != provenance.executionRuntimeProvenance {
		return fmt.Errorf("attempt execution runtime identity changed")
	}
	if err := runtime.validate(); err != nil {
		return fmt.Errorf("validate attempt execution runtime: %w", err)
	}
	runtimeWitness := provenance.executionRuntimeWitness
	if runtime.runner != runtimeWitness.runner ||
		runtime.runnerDatabase != runtimeWitness.runnerDatabase ||
		runtime.database != runtimeWitness.database ||
		!sameApplyAllSourceRevisionAdvanceRuntimeInterface(runtime.serviceGraph, runtimeWitness.serviceGraph) ||
		!sameApplyAllSourceRevisionAdvanceRuntimeDeps(runtime.applyDeps, runtimeWitness.applyDeps) ||
		!runtime.componentKindRegistrationSet.SameSnapshot(runtimeWitness.componentKindRegistrationSet) ||
		!runtime.componentKindRegistrationSet.SameSnapshot(provenance.registrations) {
		return fmt.Errorf("attempt execution runtime changed from its attempt-time witness")
	}
	if identity.applyOptions != provenance.applyOptions {
		return fmt.Errorf("attempt apply options changed from their provenance")
	}
	if identity.selectedProofIndex != provenance.selectedProofIndex ||
		identity.pendingOrdinal != provenance.pendingOrdinal {
		return fmt.Errorf("attempt pending selection changed from its provenance")
	}
	pendingIndexes := make([]int, 0, len(proof.entries))
	for index, entry := range proof.entries {
		if entry.current.pending != nil {
			pendingIndexes = append(pendingIndexes, index)
		}
	}
	if identity.pendingOrdinal < 0 || identity.pendingOrdinal >= len(pendingIndexes) ||
		pendingIndexes[identity.pendingOrdinal] != identity.selectedProofIndex {
		return fmt.Errorf("attempt selected proof entry is not its admitted pending ordinal")
	}
	if identity.installingRow != provenance.installingRow {
		return fmt.Errorf("attempt installing row changed from its provenance")
	}
	if requireMinted {
		if identity.selectedAtomClaim == nil || identity.selectedAtomClaim != provenance.selectedAtomClaim {
			return fmt.Errorf("attempt selected-atom claim changed from its provenance")
		}
	} else if identity.installingRow != (installedPackRow{}) ||
		identity.selectedAtomClaim != nil || provenance.selectedAtomClaim != nil {
		return fmt.Errorf("unminted attempt retained installing or selected-atom state")
	}
	return nil
}

func applyAllSourceRevisionAdvanceFailureMessage(err error) string {
	message := err.Error()
	if len(message) > 4000 {
		return message[:4000]
	}
	return message
}

func applyAllSourceRevisionAdvanceAttemptError(
	operation string,
	advance normalizedApplyAllSourceRevisionAdvance,
	err error,
) error {
	return applyAllSourceRevisionAdvancePlanError(
		ApplyAllSourceRevisionAdvancePlanState, advance.index, advance.Name,
		fmt.Errorf("%s exact installation attempt: %w", operation, err),
	)
}
