package controlplane

import (
	"strings"
	"testing"

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

func TestValidateApplyAllSourceRevisionAdvanceTargetEquivalenceAcceptsExactTargets(t *testing.T) {
	provider := testApplyAllSourceRevisionAdvance("provider", 4)
	consumer := testApplyAllSourceRevisionAdvance("consumer", 7)
	plan := []normalizedApplyAllSourceRevisionAdvance{provider, consumer}
	dependency := packs.PackDependency{Name: "provider", VersionConstraint: ">=2.0.0"}
	source := applyAllSourceRevisionAdvanceTuple{
		plan: append([]normalizedApplyAllSourceRevisionAdvance(nil), plan...),
		targets: map[string]*packs.SourceBackedPack{
			"provider": testApplyAllSourceRevisionTarget(t, "provider", "2.1.0", "official_repo", "provider", "target"),
			"consumer": testApplyAllSourceRevisionTarget(t, "consumer", "1.0.0", "official_repo", "consumer", "target", dependency),
		},
	}
	compatibility := applyAllSourceRevisionAdvanceTuple{
		plan: append([]normalizedApplyAllSourceRevisionAdvance(nil), plan...),
		targets: map[string]*packs.SourceBackedPack{
			"provider": testApplyAllSourceRevisionTarget(t, "provider", "2.1.0", "official_repo", "provider", "target"),
			"consumer": testApplyAllSourceRevisionTarget(t, "consumer", "1.0.0", "official_repo", "consumer", "target", dependency),
		},
	}
	err := validateApplyAllSourceRevisionAdvanceTargetEquivalence(
		testApplyAllSourceRevisionAdvanceSourceTuple(source),
		testApplyAllSourceRevisionAdvanceCompatibilityTuple(compatibility),
	)
	if err != nil {
		t.Fatalf("validate target equivalence: %v", err)
	}

	err = validateApplyAllSourceRevisionAdvanceTargetEquivalence(
		testApplyAllSourceRevisionAdvanceSourceTuple(applyAllSourceRevisionAdvanceTuple{
			plan: []normalizedApplyAllSourceRevisionAdvance{}, targets: map[string]*packs.SourceBackedPack{},
		}),
		testApplyAllSourceRevisionAdvanceCompatibilityTuple(applyAllSourceRevisionAdvanceTuple{
			plan: []normalizedApplyAllSourceRevisionAdvance{}, targets: map[string]*packs.SourceBackedPack{},
		}),
	)
	if err != nil {
		t.Fatalf("validate empty target equivalence: %v", err)
	}
	assertApplyAllSourceRevisionComposeError(
		t, validateApplyAllSourceRevisionAdvanceTargetEquivalence(
			applyAllSourceRevisionAdvanceSourceTuple{}, applyAllSourceRevisionAdvanceInstalledCompatibilityTuple{},
		), -1, "",
	)

}

func TestValidateApplyAllSourceRevisionAdvanceTargetEquivalenceRejectsFactMismatch(t *testing.T) {
	advance := testApplyAllSourceRevisionAdvance("crm", 7)
	exact := testApplyAllSourceRevisionTarget(t, "crm", "1.0.0", "official_repo", "crm", "target")
	payloadMismatch := testApplyAllSourceRevisionTarget(
		t, "crm", "1.0.0", "official_repo", "crm", "target",
		packs.PackDependency{Name: "external", VersionConstraint: ">=2.0.0"},
	)
	hostMismatch := testApplyAllSourceRevisionTargetWithHostRequirement(t, "crm", packs.HostPrimitiveRequirement{
		Kind: packs.HostPrimitiveKindProductDialect, Name: "sf-crm", Version: "1",
	})
	exactPayloadHash, err := packs.PackPayloadHash(exact)
	if err != nil {
		t.Fatalf("exact payload hash: %v", err)
	}
	hostPayloadHash, err := packs.PackPayloadHash(hostMismatch)
	if err != nil || hostPayloadHash != exactPayloadHash {
		t.Fatalf("host-only payload hashes = %q/%q, err=%v; want equal", exactPayloadHash, hostPayloadHash, err)
	}

	tests := []struct {
		name          string
		compatibility *packs.SourceBackedPack
		want          string
	}{
		{name: "metadata", compatibility: testApplyAllSourceRevisionTarget(t, "crm", "2.0.0", "official_repo", "crm", "target"), want: "metadata differ"},
		{name: "payload", compatibility: payloadMismatch, want: "payload hashes differ"},
		{name: "host requirements", compatibility: hostMismatch, want: "host-requirements digests differ"},
		{name: "noncanonical source alias", compatibility: testApplyAllSourceRevisionTarget(t, "crm", "1.0.0", "in_repo", "crm", "target"), want: "metadata differ"},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			err := validateApplyAllSourceRevisionAdvanceTargetEquivalence(
				testApplyAllSourceRevisionAdvanceSourceTuple(applyAllSourceRevisionAdvanceTuple{plan: []normalizedApplyAllSourceRevisionAdvance{advance}, targets: map[string]*packs.SourceBackedPack{"crm": exact}}),
				testApplyAllSourceRevisionAdvanceCompatibilityTuple(applyAllSourceRevisionAdvanceTuple{plan: []normalizedApplyAllSourceRevisionAdvance{advance}, targets: map[string]*packs.SourceBackedPack{"crm": test.compatibility}}),
			)
			composeErr := assertApplyAllSourceRevisionComposeError(t, err, 7, "crm")
			if !strings.Contains(composeErr.Error(), test.want) {
				t.Fatalf("compose error = %v, want containing %q", composeErr, test.want)
			}
		})
	}
}

func TestValidateApplyAllSourceRevisionAdvanceTargetEquivalenceRejectsTupleMismatch(t *testing.T) {
	advance := testApplyAllSourceRevisionAdvance("crm", 9)
	target := testApplyAllSourceRevisionTarget(t, "crm", "1.0.0", "official_repo", "crm", "target")
	exact := applyAllSourceRevisionAdvanceTuple{
		plan:    []normalizedApplyAllSourceRevisionAdvance{advance},
		targets: map[string]*packs.SourceBackedPack{"crm": target},
	}

	t.Run("plan", func(t *testing.T) {
		mismatch := exact
		mismatch.plan = append([]normalizedApplyAllSourceRevisionAdvance(nil), exact.plan...)
		mismatch.plan[0].FromRevision = "other-from"
		err := validateApplyAllSourceRevisionAdvanceTargetEquivalence(
			testApplyAllSourceRevisionAdvanceSourceTuple(exact), testApplyAllSourceRevisionAdvanceCompatibilityTuple(mismatch),
		)
		composeErr := assertApplyAllSourceRevisionComposeError(t, err, 9, "crm")
		if !strings.Contains(composeErr.Error(), "plans differ") {
			t.Fatalf("compose error = %v, want plan mismatch", composeErr)
		}
	})
	t.Run("source missing target", func(t *testing.T) {
		invalid := exact
		invalid.targets = map[string]*packs.SourceBackedPack{}
		err := validateApplyAllSourceRevisionAdvanceTargetEquivalence(
			testApplyAllSourceRevisionAdvanceSourceTuple(invalid), testApplyAllSourceRevisionAdvanceCompatibilityTuple(exact),
		)
		assertApplyAllSourceRevisionComposeError(t, err, -1, "")
	})
	t.Run("compatibility nil target", func(t *testing.T) {
		invalid := exact
		invalid.targets = map[string]*packs.SourceBackedPack{"crm": nil}
		err := validateApplyAllSourceRevisionAdvanceTargetEquivalence(
			testApplyAllSourceRevisionAdvanceSourceTuple(exact), testApplyAllSourceRevisionAdvanceCompatibilityTuple(invalid),
		)
		assertApplyAllSourceRevisionComposeError(t, err, 9, "crm")
	})
	t.Run("duplicate plan and extra target", func(t *testing.T) {
		invalid := applyAllSourceRevisionAdvanceTuple{
			plan: []normalizedApplyAllSourceRevisionAdvance{advance, advance},
			targets: map[string]*packs.SourceBackedPack{
				"crm":   target,
				"extra": testApplyAllSourceRevisionTarget(t, "extra", "1.0.0", "official_repo", "extra", "target"),
			},
		}
		err := validateApplyAllSourceRevisionAdvanceTargetEquivalence(
			testApplyAllSourceRevisionAdvanceSourceTuple(invalid), testApplyAllSourceRevisionAdvanceCompatibilityTuple(invalid),
		)
		composeErr := assertApplyAllSourceRevisionComposeError(t, err, 9, "crm")
		if !strings.Contains(composeErr.Error(), "duplicates plan name") {
			t.Fatalf("compose error = %v, want duplicate plan rejection", composeErr)
		}
	})
	t.Run("noncanonical target key", func(t *testing.T) {
		invalid := exact
		invalid.targets = map[string]*packs.SourceBackedPack{" CRM ": target}
		err := validateApplyAllSourceRevisionAdvanceTargetEquivalence(
			testApplyAllSourceRevisionAdvanceSourceTuple(exact), testApplyAllSourceRevisionAdvanceCompatibilityTuple(invalid),
		)
		assertApplyAllSourceRevisionComposeError(t, err, 9, "crm")
	})
}

func testApplyAllSourceRevisionAdvanceSourceTuple(
	tuple applyAllSourceRevisionAdvanceTuple,
) applyAllSourceRevisionAdvanceSourceTuple {
	return applyAllSourceRevisionAdvanceSourceTuple{
		applyAllSourceRevisionAdvanceTuple: tuple,
		sourceOptions:                      applyAllSourceRevisionAdvancePackSourceOptions(tuple.plan),
		hasSourceOptions:                   true,
	}
}

func testApplyAllSourceRevisionAdvanceCompatibilityTuple(
	tuple applyAllSourceRevisionAdvanceTuple,
) applyAllSourceRevisionAdvanceInstalledCompatibilityTuple {
	return applyAllSourceRevisionAdvanceInstalledCompatibilityTuple{applyAllSourceRevisionAdvanceTuple: tuple}
}

func testApplyAllSourceRevisionTargetWithHostRequirement(
	t *testing.T,
	name string,
	requirement packs.HostPrimitiveRequirement,
) *packs.SourceBackedPack {
	t.Helper()
	target, err := packs.NewSourceBackedPack(&packs.ResolvedPackSource{
		Kind: "official_repo", URI: name, Revision: "target",
		Manifest: &packs.PackManifest{
			ManifestFormatVersion: packs.SupportedManifestFormatVersion,
			Name:                  name, DisplayName: name, Category: packs.AppPack, Version: "1.0.0",
			PlatformCompatibility: packs.PlatformCompatibility{Min: "0.2.0", MaxExclusive: "0.3.0"},
			HostRequirements:      []packs.HostPrimitiveRequirement{requirement},
			Components:            []packs.ManifestComponent{},
		},
		Files: map[string][]byte{}, FileRefs: []string{},
	})
	if err != nil {
		t.Fatalf("new source-backed target with host requirement: %v", err)
	}
	return target
}
