package controlplane

import (
	"go/ast"
	"testing"
)

func TestRepairRunnerOptionsOwnSourceBackedConsumers(t *testing.T) {
	legacyOptions := parseControlplaneFuncDecl(t, "operations.go", "RunRepairWithOptions")
	assertApplyAllCallArg(t, legacyOptions, "RunRepairWithOptionsAndRunnerOptions", 3, isEmptyRunnerOptionsLiteral)

	repair := parseControlplaneFuncDecl(t, "operations.go", "RunRepairWithOptionsAndRunnerOptions")
	assertApplyAllCallArg(t, repair, "NewRunnerWithOptions", 1, func(expr ast.Expr) bool {
		return isIdentifier(expr, "runnerOptions")
	})
	if call := findFuncCallByName(repair, "NewRunner"); call != nil {
		t.Fatal("RunRepairWithOptionsAndRunnerOptions must not discard RunnerOptions through NewRunner")
	}
	if !hasActivePackBackfillOptionsLiteral(repair) {
		t.Fatal("RunRepairWithOptionsAndRunnerOptions must bind registrations and candidate apply deferrals into active-pack backfill options")
	}
	for _, backfill := range []struct {
		registered string
		discarded  []string
	}{
		{
			registered: "BackfillObjectSpecChildCloneRulePackOwnershipFromActivePacksWithOptions",
			discarded: []string{
				"BackfillObjectSpecChildCloneRulePackOwnershipFromActivePacksWithComponentKindRegistrationSet",
				"BackfillObjectSpecChildCloneRulePackOwnershipFromActivePacks",
			},
		},
		{
			registered: "BackfillObjectSpecExclusiveSiblingFieldPackOwnershipFromActivePacksWithOptions",
			discarded: []string{
				"BackfillObjectSpecExclusiveSiblingFieldPackOwnershipFromActivePacksWithComponentKindRegistrationSet",
				"BackfillObjectSpecExclusiveSiblingFieldPackOwnershipFromActivePacks",
			},
		},
		{
			registered: "BackfillLegacySourceBackedCuratedDefaultLayoutIDsFromActivePacksWithOptions",
			discarded: []string{
				"BackfillLegacySourceBackedCuratedDefaultLayoutIDsFromActivePacksWithComponentKindRegistrationSet",
				"BackfillLegacySourceBackedCuratedDefaultLayoutIDsFromActivePacks",
			},
		},
	} {
		assertApplyAllCallArg(t, repair, backfill.registered, 2, func(expr ast.Expr) bool {
			return isIdentifier(expr, "activePackBackfillOptions")
		})
		for _, discarded := range backfill.discarded {
			if call := findFuncCallByName(repair, discarded); call != nil {
				t.Fatalf("RunRepairWithOptionsAndRunnerOptions must not discard candidate apply deferrals through %s", discarded)
			}
		}
	}

	legacyLock := parseControlplaneFuncDecl(t, "operations.go", "RunRepairWithLock")
	assertApplyAllCallArg(t, legacyLock, "RunRepairWithLockAndRunnerOptions", 2, isEmptyRunnerOptionsLiteral)
	locked := parseControlplaneFuncDecl(t, "operations.go", "RunRepairWithLockAndRunnerOptions")
	assertApplyAllCallArg(t, locked, "RunRepairWithOptionsAndRunnerOptions", 2, isEmptyRepairOptionsLiteral)
	assertApplyAllCallArg(t, locked, "RunRepairWithOptionsAndRunnerOptions", 3, func(expr ast.Expr) bool {
		return isIdentifier(expr, "runnerOptions")
	})

	for _, functionName := range []string{"applyAllWithOptions", "ApplySourceConvergenceWithRunnerOptions"} {
		function := parseControlplaneFuncDecl(t, "operations.go", functionName)
		assertApplyAllCallArg(t, function, "RunRepairWithOptionsAndRunnerOptions", 3, func(expr ast.Expr) bool {
			return isIdentifier(expr, "runnerOptions")
		})
		assertApplyAllCallArg(t, function, "runCustomSettingNameMetadataRepairAfterSourceConvergence", 2, isRunnerComponentRegistrationSetCall)
	}

	customSetting := parseControlplaneFuncDecl(t, "operations.go", "runCustomSettingNameMetadataRepairAfterSourceConvergence")
	assertApplyAllCallArg(t, customSetting, "BackfillObjectMetadataCustomSettingFromActivePacksWithComponentKindRegistrationSet", 2, func(expr ast.Expr) bool {
		return isIdentifier(expr, "registrations")
	})
	if call := findFuncCallByName(customSetting, "BackfillObjectMetadataCustomSettingFromActivePacks"); call != nil {
		t.Fatal("post-convergence CustomSetting repair must not discard component registrations")
	}
}

func TestApplyAllPlansOfflineCandidateBeforeRepairAndMaterializesAfterRunnerInit(t *testing.T) {
	applyAll := parseControlplaneFuncDecl(t, "operations.go", "applyAllWithOptions")
	prepare := findFuncCallByName(applyAll, "prepareOfflineCandidateUnderApplyLock")
	repair := findFuncCallByName(applyAll, "RunRepairWithOptionsAndRunnerOptions")
	runnerInit := findFuncCallByName(applyAll, "NewRunnerWithOptions")
	materialize := findFuncCallByName(applyAll, "prepareOfflineCandidateMaterializationsUnderApplyLock")
	if prepare == nil || repair == nil || runnerInit == nil || materialize == nil {
		t.Fatal("ApplyAll must plan, repair, initialize the runner, and materialize an offline candidate")
	}
	if prepare.Pos() >= repair.Pos() {
		t.Fatal("ApplyAll must plan candidate transitions before source-backed repair")
	}
	if materialize.Pos() <= runnerInit.Pos() {
		t.Fatal("ApplyAll must keep candidate materialization after repair and runner initialization")
	}
	if characterTransitions := findFuncCallByName(applyAll, "reconcileOfflineCandidateCharacterTransitionsUnderApplyLock"); characterTransitions != nil {
		t.Fatal("ApplyAll must not infer physical character transitions from offline candidate metadata")
	}
}

func hasActivePackBackfillOptionsLiteral(fn *ast.FuncDecl) bool {
	found := false
	ast.Inspect(fn.Body, func(node ast.Node) bool {
		literal, ok := node.(*ast.CompositeLit)
		if !ok {
			return true
		}
		selector, ok := literal.Type.(*ast.SelectorExpr)
		if !ok || selector.Sel == nil || selector.Sel.Name != "ActivePackBackfillOptions" || !isIdentifier(selector.X, "packs") {
			return true
		}
		registrations := false
		deferredPackNames := false
		for _, element := range literal.Elts {
			keyValue, ok := element.(*ast.KeyValueExpr)
			if !ok {
				continue
			}
			switch {
			case isIdentifier(keyValue.Key, "ComponentKindRegistrationSet"):
				registrations = isSelector(keyValue.Value, "runnerOptions", "ComponentKindRegistrationSet")
			case isIdentifier(keyValue.Key, "DeferredPackNames"):
				deferredPackNames = isSelector(keyValue.Value, "options", "deferredSourceBackedLegacyBackfills")
			}
		}
		found = registrations && deferredPackNames
		return !found
	})
	return found
}

func isEmptyRepairOptionsLiteral(expr ast.Expr) bool {
	literal, ok := expr.(*ast.CompositeLit)
	return ok && isIdentifier(literal.Type, "RepairOptions") && len(literal.Elts) == 0
}
