package controlplane

import (
	"database/sql"
	"encoding/json"
	"strings"
	"testing"

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

func TestBuildApplyAllSourceRevisionAdvanceAttributionPreflightOverridesDerivesExactAuthority(t *testing.T) {
	entries := []applyAllSourceRevisionAdvanceExecutionProofEntry{
		testApplyAllSourceRevisionAdvanceAttributionEntry(
			t, "alpha", 3, installedPackStatusActive,
			ApplyAllSourceRevisionAdvancePendingAdvance,
		),
		testApplyAllSourceRevisionAdvanceAttributionEntry(
			t, "beta", 11, installedPackStatusFailed,
			ApplyAllSourceRevisionAdvancePendingRetry,
		),
		testApplyAllSourceRevisionAdvanceAttributionEntry(
			t, "gamma", 19, installedPackStatusActive, "",
		),
	}
	db := &sql.DB{}
	prepared, overrides := testApplyAllSourceRevisionAdvancePreparedAttribution(t, db, entries)
	registrations := prepared.admission.identity.proof.executionRuntime.componentKindRegistrationSet
	desired := overrides.plannedDesiredPacks.clone()
	if len(desired) != 3 || desired["alpha"] != entries[0].installedCompatibility ||
		desired["beta"] != entries[1].installedCompatibility ||
		desired["gamma"] != entries[2].installedCompatibility {
		t.Fatalf("desired targets = %#v, want every exact installed target", desired)
	}
	runtimeEffective := overrides.runtimeEffectivePacks.clone()
	if len(runtimeEffective) != 2 || runtimeEffective["alpha"] != entries[0].currentTarget ||
		runtimeEffective["gamma"] != entries[2].currentTarget || runtimeEffective["beta"] != nil {
		t.Fatalf("runtime-effective targets = %#v, want only exact active current targets", runtimeEffective)
	}
	retryPeers, err := bindApplyAllSourceRevisionAdvanceAttributionRetryPeers(
		overrides, entries[0].installedCompatibility, db, registrations,
	)
	if err != nil {
		t.Fatalf("bind retry peers: %v", err)
	}
	peer, ok := retryPeers["beta"]
	if len(retryPeers) != 1 || !ok || peer.row != entries[1].current.row ||
		peer.target != entries[1].installedCompatibility {
		t.Fatalf("retry peers = %#v, want exact beta row and target", retryPeers)
	}

	for _, test := range []struct {
		name          string
		db            *sql.DB
		registrations packs.ComponentKindRegistrationSet
		want          string
	}{
		{name: "database", db: &sql.DB{}, registrations: registrations, want: "runtime database"},
		{name: "registrations", db: db, registrations: runnerTestRegistrationSet(t), want: "component registrations"},
	} {
		t.Run(test.name, func(t *testing.T) {
			_, err := bindApplyAllSourceRevisionAdvanceAttributionRetryPeers(
				overrides, entries[0].installedCompatibility, test.db, test.registrations,
			)
			if err == nil || !strings.Contains(err.Error(), test.want) {
				t.Fatalf("mismatched %s error = %v", test.name, err)
			}
		})
	}

	originalDesired := overrides.plannedDesiredPacks
	overrides.plannedDesiredPacks, err = newInstallPackPreflightPackContext(originalDesired.clone())
	if err != nil {
		t.Fatalf("rebuild equivalent desired context: %v", err)
	}
	assertApplyAllSourceRevisionAdvanceAttributionBindError(
		t, overrides, entries[0].installedCompatibility, db, registrations, "desired-pack context changed",
	)
	overrides.plannedDesiredPacks = originalDesired
	originalRuntime := overrides.runtimeEffectivePacks
	overrides.runtimeEffectivePacks, err = newInstallPackPreflightPackContext(originalRuntime.clone())
	if err != nil {
		t.Fatalf("rebuild equivalent runtime context: %v", err)
	}
	assertApplyAllSourceRevisionAdvanceAttributionBindError(
		t, overrides, entries[0].installedCompatibility, db, registrations, "runtime-effective context changed",
	)
	overrides.runtimeEffectivePacks = originalRuntime

	wantBeta := originalDesired.byName["beta"]
	originalDesired.byName["beta"] = entries[0].installedCompatibility
	assertApplyAllSourceRevisionAdvanceAttributionBindError(
		t, overrides, entries[0].installedCompatibility, db, registrations, "desired-pack context changed",
	)
	originalDesired.byName["beta"] = wantBeta
	wantGamma := originalRuntime.byName["gamma"]
	delete(originalRuntime.byName, "gamma")
	assertApplyAllSourceRevisionAdvanceAttributionBindError(
		t, overrides, entries[0].installedCompatibility, db, registrations, "runtime-effective context changed",
	)
	originalRuntime.byName["gamma"] = wantGamma
	assertApplyAllSourceRevisionAdvanceAttributionBindError(
		t, overrides, entries[1].installedCompatibility, db, registrations, "exact prepared",
	)
	prepared.pendingOrdinal = 1
	assertApplyAllSourceRevisionAdvanceAttributionBindError(
		t, overrides, entries[0].installedCompatibility, db, registrations, "selection changed",
	)
}

func assertApplyAllSourceRevisionAdvanceAttributionBindError(
	t *testing.T,
	overrides *installPackPreflightOverrides,
	requested *packs.SourceBackedPack,
	db *sql.DB,
	registrations packs.ComponentKindRegistrationSet,
	want string,
) {
	t.Helper()
	_, err := bindApplyAllSourceRevisionAdvanceAttributionRetryPeers(
		overrides, requested, db, registrations,
	)
	if err == nil || !strings.Contains(err.Error(), want) {
		t.Fatalf("attribution bind error = %v, want %q", err, want)
	}
}

func testApplyAllSourceRevisionAdvancePreparedAttribution(
	t *testing.T,
	db *sql.DB,
	entries []applyAllSourceRevisionAdvanceExecutionProofEntry,
	registrationSets ...packs.ComponentKindRegistrationSet,
) (*applyAllSourceRevisionAdvanceAttemptIdentity, *installPackPreflightOverrides) {
	t.Helper()
	if len(registrationSets) > 1 {
		t.Fatalf("attribution fixture registration set count = %d, want at most one", len(registrationSets))
	}
	admission := testApplyAllSourceRevisionAdvanceExecutionAdmissionFromEntries(t, db, entries)
	if len(registrationSets) == 1 {
		runtime := admission.identity.proof.executionRuntime
		runtime.componentKindRegistrationSet = registrationSets[0]
		runtime.provenance.componentKindRegistrationSet = registrationSets[0]
		if err := validateApplyAllSourceRevisionAdvanceExecutionAdmission(admission); err != nil {
			t.Fatalf("validate attribution admission with registrations: %v", err)
		}
	}
	prepared, err := prepareApplyAllSourceRevisionAdvanceAttempt(
		admission, 0, packs.ApplyOptions{},
	)
	if err != nil {
		t.Fatalf("prepare attribution attempt: %v", err)
	}
	overrides, err := buildApplyAllSourceRevisionAdvanceAttributionPreflightOverrides(prepared)
	if err != nil {
		t.Fatalf("build attribution preflight overrides: %v", err)
	}
	return prepared, overrides
}

func testApplyAllSourceRevisionAdvanceAttributionEntry(
	t *testing.T,
	name string,
	index int,
	status string,
	pendingKind ApplyAllSourceRevisionAdvancePendingKind,
	ownedObjects ...string,
) applyAllSourceRevisionAdvanceExecutionProofEntry {
	t.Helper()
	advance := testApplyAllSourceRevisionAdvance(name, index)
	target := testApplyAllSourceRevisionAdvanceAttributionTarget(
		t, advance, advance.ToRevision, ownedObjects...,
	)
	currentTarget := target
	var applied func(*packs.SourceBackedPack) string
	var desired func(*packs.SourceBackedPack) string
	switch pendingKind {
	case ApplyAllSourceRevisionAdvancePendingAdvance:
		currentTarget = testApplyAllSourceRevisionAdvanceAttributionTarget(
			t, advance, advance.FromRevision, ownedObjects...,
		)
		applied = testCurrentTargetV1Hash
		desired = testCurrentTargetV1Hash
	case ApplyAllSourceRevisionAdvancePendingRetry:
		desired = testCurrentTargetV1Hash
	case "":
		applied = testCurrentTargetV1Hash
		desired = testCurrentTargetV1Hash
	default:
		t.Fatalf("unsupported attribution fixture pending kind %q", pendingKind)
	}
	current := testApplyAllSourceRevisionAdvanceFrozenCurrentTargetProof(
		t, advance, currentTarget, status, advance.Kind, applied, desired,
	)
	if (current.snapshot.entries[0].pending == nil) != (pendingKind == "") ||
		(current.snapshot.entries[0].pending != nil && current.snapshot.entries[0].pending.Kind != pendingKind) {
		t.Fatalf("attribution fixture pending = %#v, want %q", current.snapshot.entries[0].pending, pendingKind)
	}
	atom, err := packs.FreezeSourceBackedExecutionAtom(target)
	if err != nil {
		t.Fatalf("freeze attribution fixture atom: %v", err)
	}
	return applyAllSourceRevisionAdvanceExecutionProofEntry{
		name: name, current: current.snapshot.entries[0], currentTarget: currentTarget,
		installedCompatibility: target, atom: atom,
	}
}

func testApplyAllSourceRevisionAdvanceAttributionTarget(
	t *testing.T,
	advance normalizedApplyAllSourceRevisionAdvance,
	revision string,
	ownedObjects ...string,
) *packs.SourceBackedPack {
	t.Helper()
	if len(ownedObjects) == 0 {
		return testApplyAllSourceRevisionAdvanceBeginTarget(
			t, advance, packs.AppPack, revision,
		)
	}
	components := make([]packs.ManifestComponent, 0, len(ownedObjects))
	for _, objectAPIName := range ownedObjects {
		payload := json.RawMessage(`[{"apiName":"` + objectAPIName + `","label":"` + objectAPIName + `","tableType":"standard_object","category":"test","sharingModel":"Private","enableHierarchySharing":false,"fields":[{"apiName":"Name","label":"Name","type":"string","required":true,"isNameField":true}]}]`)
		components = append(components, packs.ManifestComponent{
			Kind: packs.ComponentKindObjectSpec, BusinessKey: strings.ToLower(objectAPIName),
			Manageability: packs.ManageabilityLocked, Payload: payload,
		})
	}
	target, err := packs.NewSourceBackedPack(&packs.ResolvedPackSource{
		Kind: advance.Kind, URI: advance.URI, Revision: revision,
		Manifest: &packs.PackManifest{
			ManifestFormatVersion: packs.SupportedManifestFormatVersion,
			Name:                  advance.Name,
			DisplayName:           advance.Name,
			Category:              packs.AppPack,
			Version:               "1.0.0",
			PlatformCompatibility: packs.PlatformCompatibility{Min: "0.2.0", MaxExclusive: "0.3.0"},
			Components:            components,
		},
		Files: map[string][]byte{}, FileRefs: []string{},
	})
	if err != nil {
		t.Fatalf("new attribution target: %v", err)
	}
	return target
}
