package controlplane

import (
	"fmt"
	"strings"

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

type applyAllSourceRevisionAdvanceTargetFingerprint struct {
	metadata               packs.PackHashMetadata
	payloadHash            string
	hostRequirementsDigest string
}

// validateApplyAllSourceRevisionAdvanceTargetEquivalence compares only durable
// desired-pack facts. It returns no capability; the execution-proof binder is
// the sole step that joins these tuples to the exact frozen current-row proof.
func validateApplyAllSourceRevisionAdvanceTargetEquivalence(
	source applyAllSourceRevisionAdvanceSourceTuple,
	compatibility applyAllSourceRevisionAdvanceInstalledCompatibilityTuple,
) error {
	if source.plan == nil || source.targets == nil {
		return applyAllSourceRevisionAdvanceTargetEquivalenceError(
			-1, "", fmt.Errorf("source target tuple was not minted by its resolver"),
		)
	}
	if compatibility.plan == nil || compatibility.targets == nil {
		return applyAllSourceRevisionAdvanceTargetEquivalenceError(
			-1, "", fmt.Errorf("compatibility target tuple was not minted by its resolver"),
		)
	}
	if err := validateApplyAllSourceRevisionAdvanceTargetTuple("source", source.applyAllSourceRevisionAdvanceTuple); err != nil {
		return err
	}
	if err := validateApplyAllSourceRevisionAdvanceTargetTuple("compatibility", compatibility.applyAllSourceRevisionAdvanceTuple); err != nil {
		return err
	}
	if len(source.plan) != len(compatibility.plan) {
		return applyAllSourceRevisionAdvanceTargetEquivalenceError(
			-1, "", fmt.Errorf("source and compatibility target plans have different lengths"),
		)
	}

	for index, advance := range source.plan {
		if compatibility.plan[index] != advance {
			return applyAllSourceRevisionAdvanceTargetEquivalenceError(
				advance.index, advance.Name, fmt.Errorf("source and compatibility target plans differ"),
			)
		}
		name := strings.ToLower(strings.TrimSpace(advance.Name))
		sourceFingerprint, err := fingerprintApplyAllSourceRevisionAdvanceTarget(source.targets[name])
		if err != nil {
			return applyAllSourceRevisionAdvanceTargetEquivalenceError(
				advance.index, advance.Name, fmt.Errorf("fingerprint source target: %w", err),
			)
		}
		compatibilityFingerprint, err := fingerprintApplyAllSourceRevisionAdvanceTarget(compatibility.targets[name])
		if err != nil {
			return applyAllSourceRevisionAdvanceTargetEquivalenceError(
				advance.index, advance.Name, fmt.Errorf("fingerprint compatibility target: %w", err),
			)
		}
		switch {
		case sourceFingerprint.metadata != compatibilityFingerprint.metadata:
			return applyAllSourceRevisionAdvanceTargetEquivalenceError(
				advance.index, advance.Name, fmt.Errorf("fresh source and installed compatibility target metadata differ"),
			)
		case sourceFingerprint.payloadHash != compatibilityFingerprint.payloadHash:
			return applyAllSourceRevisionAdvanceTargetEquivalenceError(
				advance.index, advance.Name, fmt.Errorf("fresh source and installed compatibility target payload hashes differ"),
			)
		case sourceFingerprint.hostRequirementsDigest != compatibilityFingerprint.hostRequirementsDigest:
			return applyAllSourceRevisionAdvanceTargetEquivalenceError(
				advance.index, advance.Name, fmt.Errorf("fresh source and installed compatibility target host-requirements digests differ"),
			)
		}
	}
	return nil
}

func validateApplyAllSourceRevisionAdvanceTargetTuple(label string, tuple applyAllSourceRevisionAdvanceTuple) error {
	if len(tuple.plan) != len(tuple.targets) {
		return applyAllSourceRevisionAdvanceTargetEquivalenceError(
			-1, "",
			fmt.Errorf("%s target tuple has %d plan entries and %d targets", label, len(tuple.plan), len(tuple.targets)),
		)
	}
	planByName := make(map[string]normalizedApplyAllSourceRevisionAdvance, len(tuple.plan))
	for _, advance := range tuple.plan {
		name := strings.ToLower(strings.TrimSpace(advance.Name))
		if _, duplicate := planByName[name]; duplicate {
			return applyAllSourceRevisionAdvanceTargetEquivalenceError(
				advance.index, advance.Name, fmt.Errorf("%s target tuple duplicates plan name %q", label, name),
			)
		}
		planByName[name] = advance
		target, ok := tuple.targets[name]
		if !ok || target == nil {
			return applyAllSourceRevisionAdvanceTargetEquivalenceError(
				advance.index, advance.Name,
				fmt.Errorf("%s target tuple is missing its exact target", label),
			)
		}
		if err := validateApplyAllSourceRevisionAdvanceTarget(advance, target); err != nil {
			return applyAllSourceRevisionAdvanceTargetEquivalenceError(
				advance.index, advance.Name,
				fmt.Errorf("validate %s target tuple: %w", label, err),
			)
		}
	}
	// Equal cardinality, unique plan names, and one successful canonical lookup
	// per plan entry establish an exact plan-to-target key bijection.
	ordered, err := orderApplyAllSourceRevisionAdvanceTuple(tuple.plan, tuple.targets)
	if err != nil {
		return applyAllSourceRevisionAdvanceTargetEquivalenceError(
			-1, "", fmt.Errorf("order %s target tuple: %w", label, err),
		)
	}
	for index := range tuple.plan {
		if tuple.plan[index] != ordered[index] {
			advance := tuple.plan[index]
			return applyAllSourceRevisionAdvanceTargetEquivalenceError(
				advance.index, advance.Name,
				fmt.Errorf("%s target tuple is not in dependency order", label),
			)
		}
	}
	return nil
}

func applyAllSourceRevisionAdvanceTargetEquivalenceError(index int, name string, err error) error {
	return applyAllSourceRevisionAdvancePlanError(ApplyAllSourceRevisionAdvancePlanCompose, index, name, err)
}

func fingerprintApplyAllSourceRevisionAdvanceTarget(target *packs.SourceBackedPack) (applyAllSourceRevisionAdvanceTargetFingerprint, error) {
	if target == nil {
		return applyAllSourceRevisionAdvanceTargetFingerprint{}, fmt.Errorf("target is nil")
	}
	payloadHash, err := packs.PackPayloadHash(target)
	if err != nil {
		return applyAllSourceRevisionAdvanceTargetFingerprint{}, fmt.Errorf("payload hash: %w", err)
	}
	hostRequirementsDigest, err := packs.PackHostRequirementsDigest(target)
	if err != nil {
		return applyAllSourceRevisionAdvanceTargetFingerprint{}, fmt.Errorf("host-requirements digest: %w", err)
	}
	return applyAllSourceRevisionAdvanceTargetFingerprint{
		metadata:               packs.PackHashMetadataForPack(target),
		payloadHash:            payloadHash,
		hostRequirementsDigest: hostRequirementsDigest,
	}, nil
}
