package layoutbundleslotv1

import (
	"strings"
	"testing"
)

func TestEvaluateTransitionInitialReuseAndAppend(t *testing.T) {
	_, initial := fixtureDeclaration(t, 1, "Issue", nil)
	transition, err := EvaluateTransition(nil, initial)
	if err != nil {
		t.Fatal(err)
	}
	if transition.Kind != TransitionInitial || transition.ExpectedHeadVersion != 0 || transition.ExpectedHeadRevisionDigest != "" {
		t.Fatalf("initial transition = %#v", transition)
	}
	lineage := lineageFrom(initial)
	reuse, err := EvaluateTransition(&lineage, initial)
	if err != nil || reuse.Kind != TransitionExactReuse {
		t.Fatalf("reuse = %#v, %v", reuse, err)
	}
	_, successor := fixtureDeclaration(t, 2, "IssueV2", &initial.TargetComponentID)
	appendTransition, err := EvaluateTransition(&lineage, successor)
	if err != nil {
		t.Fatal(err)
	}
	if appendTransition.Kind != TransitionAppendSuccessor || appendTransition.ExpectedHeadVersion != 1 || appendTransition.ExpectedHeadRevisionDigest != initial.RevisionDigest {
		t.Fatalf("append transition = %#v", appendTransition)
	}
}

func TestEvaluateTransitionHistoricalReuseAndTargetReturn(t *testing.T) {
	_, first := fixtureDeclaration(t, 1, "Issue", nil)
	_, second := fixtureDeclaration(t, 2, "IssueV2", &first.TargetComponentID)
	lineage := lineageFrom(first, second)
	reuse, err := EvaluateTransition(&lineage, first)
	if err != nil || reuse.Kind != TransitionExactReuse {
		t.Fatalf("historical reuse = %#v, %v", reuse, err)
	}
	_, third := fixtureDeclaration(t, 3, "Issue", &second.TargetComponentID)
	transition, err := EvaluateTransition(&lineage, third)
	if err != nil || transition.Kind != TransitionAppendSuccessor || transition.Candidate.TargetComponentID != first.TargetComponentID {
		t.Fatalf("A to B to A transition = %#v, %v", transition, err)
	}
}

func TestEvaluateTransitionRejectsGapForkAndRebinding(t *testing.T) {
	_, first := fixtureDeclaration(t, 1, "Issue", nil)
	lineage := lineageFrom(first)
	_, third := fixtureDeclaration(t, 3, "IssueV3", &first.TargetComponentID)
	if _, err := EvaluateTransition(&lineage, third); err == nil || !strings.Contains(err.Error(), "exactly") {
		t.Fatalf("gap error = %v", err)
	}
	fork := first
	fork.RevisionDigest = "sha256:" + strings.Repeat("f", 64)
	if _, err := EvaluateTransition(&lineage, fork); err == nil || !strings.Contains(err.Error(), "fork") {
		t.Fatalf("fork error = %v", err)
	}
	rebound := first
	rebound.NamespaceID = "io.example.other"
	rebound.DeclarationComponentID = "sha256:" + strings.Repeat("e", 64)
	if _, err := EvaluateTransition(&lineage, rebound); err == nil {
		t.Fatal("reservation rebinding succeeded")
	}
	if _, err := EvaluateTransition(nil, third); err == nil || !strings.Contains(err.Error(), "begin") {
		t.Fatalf("absent gap error = %v", err)
	}
}

func TestValidateLineageRejectsNonContiguousOrWrongPredecessor(t *testing.T) {
	_, first := fixtureDeclaration(t, 1, "Issue", nil)
	_, second := fixtureDeclaration(t, 2, "IssueV2", &first.TargetComponentID)
	lineage := lineageFrom(first, second)
	lineage.Revisions[1].DefinitionVersion = 3
	if err := ValidateLineage(lineage); err == nil || !strings.Contains(err.Error(), "must equal 2") {
		t.Fatalf("noncontiguous error = %v", err)
	}
	lineage = lineageFrom(first, second)
	lineage.Revisions[1].PreviousTargetComponentID = pointer("sha256:" + strings.Repeat("a", 64))
	if err := ValidateLineage(lineage); err == nil || !strings.Contains(err.Error(), "does not match") {
		t.Fatalf("predecessor error = %v", err)
	}
}

func TestEvaluateTransitionReturnsDefensivePredecessorCopy(t *testing.T) {
	_, first := fixtureDeclaration(t, 1, "Issue", nil)
	previous := first.TargetComponentID
	_, successor := fixtureDeclaration(t, 2, "IssueV2", &previous)
	lineage := lineageFrom(first)
	transition, err := EvaluateTransition(&lineage, successor)
	if err != nil {
		t.Fatal(err)
	}
	*successor.PreviousTargetComponentID = "sha256:" + strings.Repeat("f", 64)
	if transition.Candidate.PreviousTargetComponentID == nil || *transition.Candidate.PreviousTargetComponentID != previous {
		t.Fatal("transition Candidate aliases caller predecessor storage")
	}
}

func lineageFrom(declarations ...Declaration) Lineage {
	first := declarations[0]
	lineage := Lineage{
		AppID: first.AppID, NamespaceID: first.NamespaceID, LocalComponentKey: first.LocalComponentKey,
		DeclarationComponentID: first.DeclarationComponentID,
		Revisions:              make([]AcceptedRevision, len(declarations)),
	}
	for index, declaration := range declarations {
		lineage.Revisions[index] = acceptedRevision(declaration)
	}
	return lineage
}
