package main

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

// R-54 §9 rule 6 — the seam-discipline test, and the one this repo has the most reason to write:
// v0.91.0 shipped the PBS auth-probe seam with `main.go` never calling `SetAuthSink`, every unit
// test green because they all injected the seam directly. The guestnet watchdog has the identical
// shape (a component + a reporter seam + a goroutine), so its wiring is asserted here rather than
// trusted.
//
// This walks the AST rather than grepping the source: a commented-out call still satisfies a
// substring match (found while red-proofing the controller's twin of this test), and a comment is
// not a caller.
func TestMainWiresGuestNetWatchdog(t *testing.T) {
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, "main.go", nil, 0)
	if err != nil {
		t.Fatalf("parse main.go: %v", err)
	}

	var constructed, reporterWired, started bool
	ast.Inspect(f, func(n ast.Node) bool {
		switch node := n.(type) {
		case *ast.CallExpr:
			switch fn := node.Fun.(type) {
			case *ast.SelectorExpr:
				switch fn.Sel.Name {
				case "New":
					// guestnet.New(...)
					if pkg, ok := fn.X.(*ast.Ident); ok && pkg.Name == "guestnet" {
						constructed = true
					}
				case "SetGuestNetReporter":
					reporterWired = true
				}
			}
		case *ast.GoStmt:
			if sel, ok := node.Call.Fun.(*ast.SelectorExpr); ok && sel.Sel.Name == "Watch" {
				if id, ok := sel.X.(*ast.Ident); ok && id.Name == "gnWatchdog" {
					started = true
				}
			}
		}
		return true
	})

	if !constructed {
		t.Error("main.go never calls guestnet.New — the watchdog does not exist at runtime")
	}
	if !reporterWired {
		t.Error("main.go never calls collector.SetGuestNetReporter — the guest_net stanza would " +
			"never reach the hub (the exact v0.91.0 inert-seam defect)")
	}
	if !started {
		t.Error("main.go never starts the watchdog with `go gnWatchdog.Watch(ctx)` — it would be " +
			"constructed, reported on, and never probe anything")
	}
}

// The watchdog must NOT join the errc fan-out: a guest-network watchdog that can terminate the
// agent turns a customer's DHCP problem into an operator-plane outage. If it is ever changed to
// `errc <- ...`, the drain bound at the bottom of main() also has to change — this catches the
// first half of that mistake.
func TestGuestNetWatchdogIsNotInTheErrcFanout(t *testing.T) {
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, "main.go", nil, 0)
	if err != nil {
		t.Fatalf("parse main.go: %v", err)
	}
	bad := false
	ast.Inspect(f, func(n ast.Node) bool {
		send, ok := n.(*ast.SendStmt)
		if !ok {
			return true
		}
		if ch, ok := send.Chan.(*ast.Ident); !ok || ch.Name != "errc" {
			return true
		}
		call, ok := send.Value.(*ast.CallExpr)
		if !ok {
			return true
		}
		if sel, ok := call.Fun.(*ast.SelectorExpr); ok && sel.Sel.Name == "Watch" {
			if id, ok := sel.X.(*ast.Ident); ok && id.Name == "gnWatchdog" {
				bad = true
			}
		}
		return true
	})
	if bad {
		t.Fatal("the guestnet watchdog was added to the errc fan-out — a watchdog over customer " +
			"guests must never be able to bring the agent down (and the drain bound in main() " +
			"would now be off by one, hanging shutdown)")
	}
}
