package api

import (
	"go/ast"
	"go/parser"
	"go/token"
	"strings"
	"testing"
)

// TestDeployStackWiresTheLifecycleGate — the seam-discipline test (§9 rule 6).
//
// The lifecycle predicate is unit-tested in internal/stacks, and a test there passes whether or not
// deployStack ever calls it. Three inert-seam defects shipped fully-green in three days (controller
// v0.154.0, agent v0.91.0, agent v0.92.0's missing sudoers grant), all this exact shape: correct
// component, absent caller. So the CALLER is asserted here, from source.
//
// It walks the AST rather than doing strings.Contains on the file, because a commented-out call
// still contains the string — the lesson recorded in PROMPT-TEMPLATE §10.
//
// It also asserts ORDER: the gate must precede the DeployStack call, or it is not fail-closed.
func TestDeployStackWiresTheLifecycleGate(t *testing.T) {
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, "router.go", nil, 0) // comments dropped: a commented call is not a call
	if err != nil {
		t.Fatalf("parse router.go: %v", err)
	}

	var fn *ast.FuncDecl
	ast.Inspect(f, func(n ast.Node) bool {
		if d, ok := n.(*ast.FuncDecl); ok && d.Name.Name == "deployStack" {
			fn = d
			return false
		}
		return true
	})
	if fn == nil {
		t.Fatal("deployStack not found in router.go — did it move? the gate's wiring is now unasserted")
	}

	canInstallPos, deployPos := -1, -1
	ast.Inspect(fn, func(n ast.Node) bool {
		call, ok := n.(*ast.CallExpr)
		if !ok {
			return true
		}
		sel, ok := call.Fun.(*ast.SelectorExpr)
		if !ok {
			return true
		}
		off := fset.Position(call.Pos()).Offset
		switch sel.Sel.Name {
		case "CanInstall":
			if canInstallPos == -1 {
				canInstallPos = off
			}
		case "DeployStack":
			if deployPos == -1 {
				deployPos = off
			}
		}
		return true
	})

	if canInstallPos == -1 {
		t.Fatal("deployStack never calls Meta.CanInstall() — the lifecycle gate is INERT: " +
			"a withdrawn app is hidden from the catalog page but still installable by direct POST")
	}
	if deployPos == -1 {
		t.Fatal("deployStack no longer calls DeployStack — this test's ordering assertion is meaningless")
	}
	if canInstallPos > deployPos {
		t.Fatalf("the lifecycle gate (offset %d) runs AFTER DeployStack (offset %d) — a gate that "+
			"fires after the mutation is not fail-closed", canInstallPos, deployPos)
	}
}

// TestLifecycleRefusalMessageIsCustomerFacingHungarian: the refusal text reaches the customer via
// showAlert(), so it must be the sentence the spec ruled, not a Go error string.
func TestLifecycleRefusalMessageIsCustomerFacingHungarian(t *testing.T) {
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, "router.go", nil, 0)
	if err != nil {
		t.Fatal(err)
	}
	const want = "Ez az alkalmazás jelenleg nem telepíthető."
	found := false
	ast.Inspect(f, func(n ast.Node) bool {
		if lit, ok := n.(*ast.BasicLit); ok && lit.Kind == token.STRING && strings.Contains(lit.Value, want) {
			found = true
		}
		return true
	})
	if !found {
		t.Fatalf("the ruled refusal message %q is not present in router.go", want)
	}
}
