package controlplane

import (
	"go/ast"
	"testing"
)

// TestRebuildDerivedDeferGuardsOnlyFormulaViewRebuild pins the
// DeferFormulaViewRebuild contract at the AST level:
//
//  1. the exported RebuildDerived keeps the always-rebuild behavior by
//     delegating to rebuildDerived with a literal false;
//  2. ApplyAll's rebuild-derived phase threads the option
//     (options.DeferFormulaViewRebuild) instead of hardcoding either mode;
//  3. rebuildDerived still contains the RebuildAllViews call — the defer path
//     must SKIP it conditionally, never remove it.
func TestRebuildDerivedDeferGuardsOnlyFormulaViewRebuild(t *testing.T) {
	exported := parseControlplaneFuncDecl(t, "operations.go", "RebuildDerived")
	delegation := findFuncCallByName(exported, "rebuildDerived")
	if delegation == nil {
		t.Fatal("RebuildDerived must delegate to rebuildDerived")
	}
	if len(delegation.Args) != 4 {
		t.Fatalf("rebuildDerived delegation args = %d, want 4", len(delegation.Args))
	}
	if lit, ok := delegation.Args[2].(*ast.Ident); !ok || lit.Name != "false" {
		t.Fatalf("RebuildDerived must pass deferFormulaViews=false, got %T", delegation.Args[2])
	}
	if lit, ok := delegation.Args[3].(*ast.Ident); !ok || lit.Name != "false" {
		t.Fatalf("the exported unserialized RebuildDerived must pass settleRebuildDebt=false (receipt mutations are lock-serialized), got %T", delegation.Args[3])
	}

	applyAll := parseControlplaneFuncDecl(t, "operations.go", "applyAllWithOptions")
	applyCall := findFuncCallByName(applyAll, "rebuildDerived")
	if applyCall == nil {
		t.Fatal("applyAllWithOptions must call rebuildDerived")
	}
	sel, ok := applyCall.Args[2].(*ast.SelectorExpr)
	if !ok || sel.Sel.Name != "DeferFormulaViewRebuild" {
		t.Fatalf("applyAllWithOptions must thread options.DeferFormulaViewRebuild, got %T", applyCall.Args[2])
	}
	if lit, ok := applyCall.Args[3].(*ast.Ident); !ok || lit.Name != "true" {
		t.Fatalf("applyAllWithOptions holds the apply lock and must settle (settleRebuildDebt=true), got %T", applyCall.Args[3])
	}
	// The deployment's designated settlement path must settle under its lock.
	withLock := parseControlplaneFuncDecl(t, "operations.go", "RebuildDerivedWithLockAndRunnerOptions")
	lockCall := findFuncCallByName(withLock, "rebuildDerived")
	if lockCall == nil {
		t.Fatal("RebuildDerivedWithLockAndRunnerOptions must call rebuildDerived directly (settlement requires the lock it holds)")
	}
	if lit, ok := lockCall.Args[3].(*ast.Ident); !ok || lit.Name != "true" {
		t.Fatalf("RebuildDerivedWithLockAndRunnerOptions must pass settleRebuildDebt=true, got %T", lockCall.Args[3])
	}
	preconditionCall := findFuncCallByName(applyAll, "requireDeferFormulaViewPrecondition")
	if preconditionCall == nil {
		t.Fatal("applyAllWithOptions must run the read-only defer precondition before any mutation")
	}
	// The precondition must be GATED on the option (an unconditional call
	// would fail normal applies on drifted databases that RebuildAllViews
	// heals today) and must PRECEDE MarkControlPlaneApplyAllRunning in
	// source order (running it later breaks the no-marker read-only retry
	// story).
	gated := false
	ast.Inspect(applyAll, func(n ast.Node) bool {
		ifStmt, ok := n.(*ast.IfStmt)
		if !ok {
			return true
		}
		sel, ok := ifStmt.Cond.(*ast.SelectorExpr)
		if !ok || sel.Sel.Name != "DeferFormulaViewRebuild" {
			return true
		}
		ast.Inspect(ifStmt.Body, func(inner ast.Node) bool {
			if call, ok := inner.(*ast.CallExpr); ok {
				if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "requireDeferFormulaViewPrecondition" {
					gated = true
				}
			}
			return true
		})
		return true
	})
	if !gated {
		t.Fatal("requireDeferFormulaViewPrecondition must be called inside an options.DeferFormulaViewRebuild guard")
	}
	markCall := findFuncCallByName(applyAll, "MarkControlPlaneApplyAllRunning")
	if markCall == nil {
		t.Fatal("applyAllWithOptions must stamp MarkControlPlaneApplyAllRunning")
	}
	if preconditionCall.Pos() >= markCall.Pos() {
		t.Fatal("the defer precondition must run BEFORE MarkControlPlaneApplyAllRunning (read-only, no marker on failure)")
	}
	// Write-ahead intent: the durable rebuild-owed receipt must be stamped
	// BEFORE MarkControlPlaneApplyAllRunning (i.e. before ANY mutation).
	// Stamping it any later opens a crash window where the apply mutated
	// state but no receipt exists, letting a subsequent non-defer auto-start
	// rebuild views under the previous binary's serving authority. A second,
	// idempotent re-stamp must sit just before the completed marker
	// (belt-and-suspenders: receipt mutations are lock-serialized, but
	// completed must never be stamped with the debt invisible).
	var owedStamps []*ast.CallExpr
	ast.Inspect(applyAll, func(n ast.Node) bool {
		if call, ok := n.(*ast.CallExpr); ok {
			if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "markFormulaViewRebuildOwed" {
				owedStamps = append(owedStamps, call)
			}
		}
		return true
	})
	if len(owedStamps) != 2 {
		t.Fatalf("applyAllWithOptions must stamp the rebuild-owed receipt exactly twice (entry write-ahead + pre-completed re-stamp), found %d", len(owedStamps))
	}
	if owedStamps[0].Pos() >= markCall.Pos() {
		t.Fatal("the entry receipt must be stamped BEFORE MarkControlPlaneApplyAllRunning (write-ahead intent: no mutation may precede the receipt)")
	}
	completedCall := findFuncCallByName(applyAll, "MarkControlPlaneApplyAllCompleted")
	if completedCall == nil {
		t.Fatal("applyAllWithOptions must stamp MarkControlPlaneApplyAllCompleted")
	}
	if owedStamps[1].Pos() <= markCall.Pos() || owedStamps[1].Pos() >= completedCall.Pos() {
		t.Fatal("the defense-in-depth re-stamp must sit between the running and completed markers")
	}
	// Both stamps must be gated on the defer option — an unconditional stamp
	// would wedge every normal auto-start apply behind a phantom debt.
	gatedStamps := 0
	ast.Inspect(applyAll, func(n ast.Node) bool {
		ifStmt, ok := n.(*ast.IfStmt)
		if !ok {
			return true
		}
		sel, ok := ifStmt.Cond.(*ast.SelectorExpr)
		if !ok || sel.Sel.Name != "DeferFormulaViewRebuild" {
			return true
		}
		ast.Inspect(ifStmt.Body, func(inner ast.Node) bool {
			if call, ok := inner.(*ast.CallExpr); ok {
				if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "markFormulaViewRebuildOwed" {
					gatedStamps++
				}
			}
			return true
		})
		return true
	})
	if gatedStamps != 2 {
		t.Fatalf("both markFormulaViewRebuildOwed stamps must be inside an options.DeferFormulaViewRebuild guard, found %d gated", gatedStamps)
	}
	// Closed lifecycle: the auto-start skip branch must consult the debt and
	// the non-defer full rebuild must settle it.
	owedReads := 0
	ast.Inspect(applyAll, func(n ast.Node) bool {
		if call, ok := n.(*ast.CallExpr); ok {
			if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "FormulaViewRebuildOwed" {
				owedReads++
			}
		}
		return true
	})
	if owedReads < 2 {
		t.Fatalf("auto-start must check the rebuild debt on BOTH the skip branch and the post-apply success tail (found %d reads); the HasPending promotion path must not return success with debt outstanding", owedReads)
	}

	body := parseControlplaneFuncDecl(t, "operations.go", "rebuildDerived")
	if findFuncCallByName(body, "RebuildAllViews") == nil {
		t.Fatal("rebuildDerived must retain the RebuildAllViews call (defer skips it conditionally)")
	}
	if findFuncCallByName(body, "formulaViewExists") == nil {
		t.Fatal("rebuildDerived's defer path must probe live view existence for its assert-only check")
	}
	if findFuncCallByName(body, "RebuildView") != nil {
		t.Fatal("rebuildDerived's defer path must NEVER create or replace views (entry precondition + never-create contract)")
	}
	if findFuncCallByName(body, "EnsureViews") != nil {
		t.Fatal("rebuildDerived's defer path must NEVER create views (entry precondition + never-create contract)")
	}
	if findFuncCallByName(body, "clearFormulaViewRebuildOwed") == nil {
		t.Fatal("the settle-enabled non-defer full rebuild must settle the formula-view rebuild debt")
	}
	usesGuard := false
	ast.Inspect(body, func(n ast.Node) bool {
		if ident, ok := n.(*ast.Ident); ok && ident.Name == "deferFormulaViews" {
			usesGuard = true
		}
		return true
	})
	if !usesGuard {
		t.Fatal("rebuildDerived must guard the formula-view pass on deferFormulaViews")
	}
}

// TestPackMaterializationThreadsDeferFlag pins the second and third apply-time
// view-replacement paths (pack install/reconcile and source-revision advance
// activation): both must thread the defer option into
// materializeSourceBackedPackDerivedWithVerification, whose loop must consult
// the flag and retain both the existence probe and the RebuildView call. The
// verified and stopped-source-set wrappers must select their exact mode.
func TestPackMaterializationThreadsDeferFlag(t *testing.T) {
	materialize := parseControlplaneFuncDecl(t, "source_pack_ops_formula_views.go", "materializeSourceBackedPackDerivedWithVerification")
	if findFuncCallByName(materialize, "RebuildView") == nil {
		t.Fatal("materializeSourceBackedPackDerived must retain RebuildView (defer skips existing views conditionally)")
	}
	if findFuncCallByName(materialize, "formulaViewExists") == nil {
		t.Fatal("materializeSourceBackedPackDerived's defer path must probe view existence (skip existing, fail closed on missing)")
	}
	usesFlag := false
	ast.Inspect(materialize, func(n ast.Node) bool {
		if ident, ok := n.(*ast.Ident); ok && ident.Name == "deferReplacement" {
			usesFlag = true
		}
		return true
	})
	if !usesFlag {
		t.Fatal("materializeSourceBackedPackDerived must guard replacement on deferReplacement")
	}
	verifiedWrapper := parseControlplaneFuncDecl(t, "source_pack_ops_formula_views.go", "materializeSourceBackedPackDerived")
	verifiedCall := findFuncCallByName(verifiedWrapper, "materializeSourceBackedPackDerivedWithVerification")
	if verifiedCall == nil || len(verifiedCall.Args) != 5 {
		t.Fatal("materializeSourceBackedPackDerived must delegate to the shared materializer")
	}
	if mode, ok := verifiedCall.Args[4].(*ast.Ident); !ok || mode.Name != "true" {
		t.Fatalf("materializeSourceBackedPackDerived must retain verification mode, got %T", verifiedCall.Args[4])
	}
	applyOnlyWrapper := parseControlplaneFuncDecl(t, "source_pack_ops_formula_views.go", "materializeSourceBackedPackDerivedApplyOnly")
	applyOnlyCall := findFuncCallByName(applyOnlyWrapper, "materializeSourceBackedPackDerivedWithVerification")
	if applyOnlyCall == nil || len(applyOnlyCall.Args) != 5 {
		t.Fatal("materializeSourceBackedPackDerivedApplyOnly must delegate to the shared materializer")
	}
	if mode, ok := applyOnlyCall.Args[4].(*ast.Ident); !ok || mode.Name != "false" {
		t.Fatalf("materializeSourceBackedPackDerivedApplyOnly must disable verification mode, got %T", applyOnlyCall.Args[4])
	}

	install := parseControlplaneFuncDecl(t, "source_pack_ops.go", "applyResolvedSourceBackedPackUnlockedWithPreflightOverrides")
	installCall := findFuncCallByName(install, "materializeSourceBackedPackDerived")
	if installCall == nil {
		t.Fatal("pack install path must call materializeSourceBackedPackDerived")
	}
	if sel, ok := installCall.Args[3].(*ast.SelectorExpr); !ok || sel.Sel.Name != "DeferFormulaViewRebuild" {
		t.Fatalf("pack install path must thread options.DeferFormulaViewRebuild, got %T", installCall.Args[3])
	}

	advance := parseControlplaneFuncDecl(t, "apply_all_source_revision_advance_activation_preparation.go", "prepareApplyAllSourceRevisionAdvanceActivationWith")
	advanceCall := findFuncCallByName(advance, "materializeDerived")
	if advanceCall == nil {
		t.Fatal("source-revision advance activation must call materializeDerived")
	}
	if sel, ok := advanceCall.Args[3].(*ast.SelectorExpr); !ok || sel.Sel.Name != "DeferFormulaViewRebuild" {
		t.Fatalf("advance activation must thread applyDeps.Options.DeferFormulaViewRebuild, got %T", advanceCall.Args[3])
	}
}

// TestOperatorEntrypointsStripDeferFlag pins that the defer contract cannot
// leak into operator install/reapply paths: each public entrypoint must zero
// DeferFormulaViewRebuild before proceeding (the entry precondition only
// guards ApplyAll, so honoring the flag here would fail closed with wrong
// cause attribution).
func TestOperatorEntrypointsStripDeferFlag(t *testing.T) {
	for _, name := range []string{
		"InstallPackFromSourceWithOptions",
		"ReapplyPackFromSource",
		"ReapplyPackFromSourceWithSchemaReadinessAndRunnerOptions",
	} {
		fn := parseControlplaneFuncDecl(t, "source_pack_ops.go", name)
		stripped := false
		ast.Inspect(fn, func(n ast.Node) bool {
			assign, ok := n.(*ast.AssignStmt)
			if !ok || len(assign.Lhs) != 1 {
				return true
			}
			sel, ok := assign.Lhs[0].(*ast.SelectorExpr)
			if !ok || sel.Sel.Name != "DeferFormulaViewRebuild" {
				return true
			}
			if ident, ok := assign.Rhs[0].(*ast.Ident); ok && ident.Name == "false" {
				stripped = true
			}
			return true
		})
		if !stripped {
			t.Errorf("%s must set options.DeferFormulaViewRebuild = false (operator paths keep always-rebuild)", name)
		}
	}
}
