package layoutbundleslotv1

import (
	"fmt"
	"math"

	"github.com/pingcap-inc/nexuscrm-contract/pkg/appauthority"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/layoutbundlekeyv1"
)

// ValidateLineage validates one complete, contiguous, immutable App-slot
// registration history. The zero value is not a lineage; absence is represented
// by a nil *Lineage when evaluating a transition.
func ValidateLineage(lineage Lineage) error {
	if err := validateLineageIdentity(lineage); err != nil {
		return err
	}
	if lineage.Revisions == nil || len(lineage.Revisions) == 0 {
		return fmt.Errorf("layout-bundle slot lineage revisions must be present and non-empty")
	}
	for index, revision := range lineage.Revisions {
		expectedVersion := uint64(index) + 1
		if revision.DefinitionVersion != expectedVersion {
			return fmt.Errorf("layout-bundle slot lineage revision[%d] definition version must equal %d", index, expectedVersion)
		}
		if err := appauthority.ValidateDigest(revision.RevisionDigest); err != nil {
			return fmt.Errorf("layout-bundle slot lineage revision[%d] digest: %w", index, err)
		}
		if err := appauthority.ValidateDigest(revision.TargetComponentID); err != nil {
			return fmt.Errorf("layout-bundle slot lineage revision[%d] target: %w", index, err)
		}
		if index == 0 {
			if revision.PreviousTargetComponentID != nil {
				return fmt.Errorf("layout-bundle slot lineage revision[0] must omit its predecessor")
			}
			continue
		}
		if revision.PreviousTargetComponentID == nil {
			return fmt.Errorf("layout-bundle slot lineage revision[%d] requires its predecessor", index)
		}
		if err := appauthority.ValidateDigest(*revision.PreviousTargetComponentID); err != nil {
			return fmt.Errorf("layout-bundle slot lineage revision[%d] predecessor: %w", index, err)
		}
		priorTarget := lineage.Revisions[index-1].TargetComponentID
		if *revision.PreviousTargetComponentID != priorTarget {
			return fmt.Errorf("layout-bundle slot lineage revision[%d] predecessor does not match definition version %d target", index, expectedVersion-1)
		}
		if revision.TargetComponentID == priorTarget {
			return fmt.Errorf("layout-bundle slot lineage revision[%d] does not change target identity", index)
		}
	}
	return nil
}

// EvaluateTransition decides whether a verified declaration creates an absent
// lineage, exactly reuses any historical version, or appends the sole next
// successor. It never mutates current or candidate pointer storage.
func EvaluateTransition(current *Lineage, candidate Declaration) (Transition, error) {
	if err := validateDeclaration(candidate); err != nil {
		return Transition{}, err
	}
	candidateRevision := acceptedRevision(candidate)
	if current == nil {
		if candidate.DefinitionVersion != 1 {
			return Transition{}, fmt.Errorf("an absent layout-bundle slot lineage must begin at definition version 1")
		}
		return Transition{Kind: TransitionInitial, Candidate: candidateRevision}, nil
	}
	if err := ValidateLineage(*current); err != nil {
		return Transition{}, err
	}
	if candidate.AppID != current.AppID ||
		candidate.NamespaceID != current.NamespaceID ||
		candidate.LocalComponentKey != current.LocalComponentKey ||
		candidate.DeclarationComponentID != current.DeclarationComponentID {
		return Transition{}, fmt.Errorf("layout-bundle slot candidate attempts to rebind an immutable reservation")
	}
	head := current.Revisions[len(current.Revisions)-1]
	if candidate.DefinitionVersion <= head.DefinitionVersion {
		stored := current.Revisions[candidate.DefinitionVersion-1]
		if !equalAcceptedRevision(stored, candidateRevision) {
			return Transition{}, fmt.Errorf("layout-bundle slot definition version %d is a divergent fork", candidate.DefinitionVersion)
		}
		return Transition{Kind: TransitionExactReuse, Candidate: cloneAcceptedRevision(candidateRevision)}, nil
	}
	if head.DefinitionVersion == math.MaxUint64 {
		return Transition{}, fmt.Errorf("layout-bundle slot lineage definition version overflow")
	}
	if candidate.DefinitionVersion != head.DefinitionVersion+1 {
		return Transition{}, fmt.Errorf("layout-bundle slot successor must be exactly head definition version plus one")
	}
	if candidate.PreviousTargetComponentID == nil || *candidate.PreviousTargetComponentID != head.TargetComponentID {
		return Transition{}, fmt.Errorf("layout-bundle slot successor predecessor does not match the prior version target")
	}
	return Transition{
		Kind:                       TransitionAppendSuccessor,
		ExpectedHeadVersion:        head.DefinitionVersion,
		ExpectedHeadRevisionDigest: head.RevisionDigest,
		Candidate:                  candidateRevision,
	}, nil
}

func validateLineageIdentity(lineage Lineage) error {
	if err := appauthority.ValidateAppID(lineage.AppID); err != nil {
		return fmt.Errorf("layout-bundle slot lineage AppID: %w", err)
	}
	if err := appauthority.ValidateNamespaceID(lineage.NamespaceID); err != nil {
		return fmt.Errorf("layout-bundle slot lineage namespace: %w", err)
	}
	if err := layoutbundlekeyv1.ValidateLocalComponentKey(lineage.LocalComponentKey); err != nil {
		return fmt.Errorf("layout-bundle slot lineage local key: %w", err)
	}
	expectedID := appauthority.ComponentID(lineage.NamespaceID, ComponentKind, string(lineage.LocalComponentKey))
	if lineage.DeclarationComponentID != expectedID {
		return fmt.Errorf("layout-bundle slot lineage declaration ComponentID does not match its immutable identity")
	}
	return nil
}

func validateDeclaration(declaration Declaration) error {
	identity := Lineage{
		AppID: declaration.AppID, NamespaceID: declaration.NamespaceID,
		LocalComponentKey: declaration.LocalComponentKey, DeclarationComponentID: declaration.DeclarationComponentID,
	}
	if err := validateLineageIdentity(identity); err != nil {
		return err
	}
	if declaration.DefinitionVersion == 0 {
		return fmt.Errorf("layout-bundle slot declaration definition version must be positive")
	}
	if err := appauthority.ValidateDigest(declaration.TargetComponentID); err != nil {
		return fmt.Errorf("layout-bundle slot declaration target: %w", err)
	}
	if declaration.DefinitionVersion == 1 {
		if declaration.PreviousTargetComponentID != nil {
			return fmt.Errorf("layout-bundle slot declaration definition version 1 must omit its predecessor")
		}
	} else {
		if declaration.PreviousTargetComponentID == nil {
			return fmt.Errorf("layout-bundle slot declaration definition version above 1 requires its predecessor")
		}
		if err := appauthority.ValidateDigest(*declaration.PreviousTargetComponentID); err != nil {
			return fmt.Errorf("layout-bundle slot declaration predecessor: %w", err)
		}
		if *declaration.PreviousTargetComponentID == declaration.TargetComponentID {
			return fmt.Errorf("layout-bundle slot declaration successor must change target identity")
		}
	}
	if err := appauthority.ValidateDigest(declaration.BehaviorDigest); err != nil {
		return fmt.Errorf("layout-bundle slot declaration behavior digest: %w", err)
	}
	if err := appauthority.ValidateDigest(declaration.RevisionDigest); err != nil {
		return fmt.Errorf("layout-bundle slot declaration revision digest: %w", err)
	}
	return nil
}

func acceptedRevision(declaration Declaration) AcceptedRevision {
	return AcceptedRevision{
		DefinitionVersion: declaration.DefinitionVersion, RevisionDigest: declaration.RevisionDigest,
		TargetComponentID:         declaration.TargetComponentID,
		PreviousTargetComponentID: cloneStringPointer(declaration.PreviousTargetComponentID),
	}
}

func equalAcceptedRevision(left, right AcceptedRevision) bool {
	return left.DefinitionVersion == right.DefinitionVersion &&
		left.RevisionDigest == right.RevisionDigest &&
		left.TargetComponentID == right.TargetComponentID &&
		equalStringPointers(left.PreviousTargetComponentID, right.PreviousTargetComponentID)
}

func cloneAcceptedRevision(revision AcceptedRevision) AcceptedRevision {
	revision.PreviousTargetComponentID = cloneStringPointer(revision.PreviousTargetComponentID)
	return revision
}

func cloneStringPointer(value *string) *string {
	if value == nil {
		return nil
	}
	copyValue := *value
	return &copyValue
}

func equalStringPointers(left, right *string) bool {
	if left == nil || right == nil {
		return left == right
	}
	return *left == *right
}
