package controlplane

import (
	"fmt"
	"strings"
)

// validateApplyAllSourceRevisionAdvanceCurrentAtTargetCompatibility proves
// that each frozen current object already at To is the same durable object as
// its installed-compatibility target. It returns no capability and performs no
// reads or writes.
func validateApplyAllSourceRevisionAdvanceCurrentAtTargetCompatibility(
	precondition applyAllSourceRevisionAdvancePreconditionProof,
	compatibility applyAllSourceRevisionAdvanceInstalledCompatibilityTuple,
) error {
	if err := validateApplyAllSourceRevisionAdvanceFrozenCurrentTargetFacts(precondition); err != nil {
		return err
	}
	if compatibility.plan == nil || compatibility.targets == nil {
		return applyAllSourceRevisionAdvanceTargetEquivalenceError(
			-1, "", fmt.Errorf("installed compatibility target tuple was not minted by its resolver"),
		)
	}
	if err := validateApplyAllSourceRevisionAdvanceTargetTuple(
		"installed compatibility", compatibility.applyAllSourceRevisionAdvanceTuple,
	); err != nil {
		return err
	}
	if len(precondition.snapshot.entries) != len(compatibility.plan) {
		return applyAllSourceRevisionAdvanceTargetEquivalenceError(
			-1, "", fmt.Errorf("frozen current proof and installed compatibility target plans have different lengths"),
		)
	}

	compatibilityPlan := make(map[string]normalizedApplyAllSourceRevisionAdvance, len(compatibility.plan))
	for _, advance := range compatibility.plan {
		compatibilityPlan[strings.ToLower(strings.TrimSpace(advance.Name))] = advance
	}
	for _, entry := range precondition.snapshot.entries {
		advance := entry.advance
		name := strings.ToLower(strings.TrimSpace(advance.Name))
		compatibilityAdvance, ok := compatibilityPlan[name]
		if !ok || compatibilityAdvance != advance {
			return applyAllSourceRevisionAdvanceTargetEquivalenceError(
				advance.index, advance.Name, fmt.Errorf("frozen current and installed compatibility target plans differ"),
			)
		}
		if strings.TrimSpace(entry.row.SourceRevision.String) != advance.ToRevision {
			continue
		}

		currentFingerprint, err := fingerprintApplyAllSourceRevisionAdvanceTarget(precondition.current.targets[name])
		if err != nil {
			return applyAllSourceRevisionAdvanceCurrentAtTargetStateError(
				advance.index, advance.Name, fmt.Errorf("fingerprint current-at-target object: %w", err),
			)
		}
		compatibilityFingerprint, err := fingerprintApplyAllSourceRevisionAdvanceTarget(compatibility.targets[name])
		if err != nil {
			return applyAllSourceRevisionAdvanceTargetEquivalenceError(
				advance.index, advance.Name, fmt.Errorf("fingerprint installed compatibility target: %w", err),
			)
		}
		switch {
		case currentFingerprint.metadata != compatibilityFingerprint.metadata:
			return applyAllSourceRevisionAdvanceCurrentAtTargetStateError(
				advance.index, advance.Name, fmt.Errorf("current-at-target and installed compatibility target metadata differ"),
			)
		case currentFingerprint.payloadHash != compatibilityFingerprint.payloadHash:
			return applyAllSourceRevisionAdvanceCurrentAtTargetStateError(
				advance.index, advance.Name, fmt.Errorf("current-at-target and installed compatibility target payload hashes differ"),
			)
		case currentFingerprint.hostRequirementsDigest != compatibilityFingerprint.hostRequirementsDigest:
			return applyAllSourceRevisionAdvanceCurrentAtTargetStateError(
				advance.index, advance.Name, fmt.Errorf("current-at-target and installed compatibility target host-requirements digests differ"),
			)
		}
	}
	return nil
}

func applyAllSourceRevisionAdvanceCurrentAtTargetStateError(index int, name string, err error) error {
	return applyAllSourceRevisionAdvancePlanError(ApplyAllSourceRevisionAdvancePlanState, index, name, err)
}
