package controlplane

import (
	"context"
	"fmt"
	"strings"

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

// applyAllSourceRevisionAdvancePreconditionProof is minted only after the
// complete current tuple has been reconstructed from the frozen installed-row
// and active-runtime observations and every advance payload has been proven.
type applyAllSourceRevisionAdvancePreconditionProof struct {
	snapshot      applyAllSourceRevisionAdvanceCurrentSnapshot
	activeRuntime applyAllSourceRevisionAdvanceActiveRuntimeObservation
	current       applyAllSourceRevisionAdvanceTuple
	replay        applyAllSourceRevisionAdvanceTuple
}

func proveApplyAllSourceRevisionAdvancePreconditions(
	ctx context.Context,
	r *Runner,
	plan []normalizedApplyAllSourceRevisionAdvance,
) (applyAllSourceRevisionAdvancePreconditionProof, error) {
	db := runnerSQLDB(r)
	snapshot, err := readApplyAllSourceRevisionAdvanceCurrentSnapshot(ctx, db, plan)
	if err != nil {
		return applyAllSourceRevisionAdvancePreconditionProof{}, err
	}
	var activeRuntime applyAllSourceRevisionAdvanceActiveRuntimeObservation
	if len(snapshot.entries) != 0 {
		activeRuntime, err = readApplyAllSourceRevisionAdvanceActiveRuntimeObservation(ctx, db)
		if err != nil {
			return applyAllSourceRevisionAdvancePreconditionProof{}, applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanRead, -1, "", fmt.Errorf("read active runtime observation: %w", err),
			)
		}
	}
	current, err := resolveApplyAllSourceRevisionAdvanceCurrentInstalledTuple(ctx, r, snapshot, activeRuntime)
	if err != nil {
		return applyAllSourceRevisionAdvancePreconditionProof{}, err
	}
	if err := validateApplyAllSourceRevisionAdvanceCurrentPayloads(snapshot, current); err != nil {
		return applyAllSourceRevisionAdvancePreconditionProof{}, err
	}
	replay, err := resolveApplyAllSourceRevisionAdvanceReplayTuple(ctx, r, snapshot, activeRuntime, current)
	if err != nil {
		return applyAllSourceRevisionAdvancePreconditionProof{}, err
	}
	return applyAllSourceRevisionAdvancePreconditionProof{
		snapshot: snapshot, activeRuntime: activeRuntime, current: current, replay: replay,
	}, nil
}

func validateApplyAllSourceRevisionAdvanceCurrentPayloads(
	snapshot applyAllSourceRevisionAdvanceCurrentSnapshot,
	current applyAllSourceRevisionAdvanceTuple,
) error {
	if len(snapshot.entries) != len(current.plan) || len(snapshot.entries) != len(current.targets) {
		return applyAllSourceRevisionAdvancePlanError(
			ApplyAllSourceRevisionAdvancePlanState, -1, "",
			fmt.Errorf("current tuple does not match the frozen installed snapshot"),
		)
	}
	for index, entry := range snapshot.entries {
		advance := entry.advance
		if current.plan[index] != advance {
			return applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanState, advance.index, advance.Name,
				fmt.Errorf("current tuple plan does not match the frozen installed snapshot"),
			)
		}
		target := current.targets[strings.ToLower(advance.Name)]
		expected := advance
		expected.ToRevision = strings.TrimSpace(entry.row.SourceRevision.String)
		if err := validateApplyAllSourceRevisionAdvanceTarget(expected, target); err != nil {
			return applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanState, advance.index, advance.Name,
				fmt.Errorf("validate current installed payload target: %w", err),
			)
		}
		if entry.pending == nil {
			continue
		}
		switch entry.pending.Kind {
		case ApplyAllSourceRevisionAdvancePendingAdvance:
			storedHash := strings.TrimSpace(entry.row.AppliedPayloadHash.String)
			if !entry.row.AppliedPayloadHash.Valid || storedHash == "" {
				return applyAllSourceRevisionAdvancePlanError(
					ApplyAllSourceRevisionAdvancePlanState, advance.index, advance.Name,
					fmt.Errorf("installed AppliedPayloadHash is empty at source revision %q", expected.ToRevision),
				)
			}
			if _, err := packs.ValidateStoredPackPayloadHash(target, storedHash); err != nil {
				return applyAllSourceRevisionAdvancePlanError(
					ApplyAllSourceRevisionAdvancePlanState, advance.index, advance.Name,
					fmt.Errorf("installed AppliedPayloadHash does not match source revision %q payload: %w", expected.ToRevision, err),
				)
			}
		case ApplyAllSourceRevisionAdvancePendingRetry,
			ApplyAllSourceRevisionAdvancePendingCanonicalize:
			// Retry rows may have no applied hash, and canonicalization does not
			// authorize a source-revision change. The exact reread binds both.
		default:
			return applyAllSourceRevisionAdvancePlanError(
				ApplyAllSourceRevisionAdvancePlanState, advance.index, advance.Name,
				fmt.Errorf("unsupported pending source revision advance kind %q", entry.pending.Kind),
			)
		}
	}
	return nil
}
