package runs

// P8-REVIEW axis 3, REFUTER probe against the finder's F5 ("the second guard of `run abandon` cannot
// be predicted from the operator's table").
//
// The guard is `unit != "" || baseline != nil` (pgstore/runs.go:465). The table prints UNIT for the
// first and SPENT for the second, and SPENT is "?" EXACTLY when the baseline is NULL — the column is
// `case when a.spend_baseline_micro_usd is null then null else ... end` (runs.go:374-375), so the
// mapping is a bijection, not an inference. The operator's runbook states both halves of the guard
// in prose: deploy/README.md:247 "Команда ОТКАЖЕТ, если попытка ещё называет юнит ИЛИ несёт базовую
// линию траты".
//
// This asserts the bijection over all four states of the two columns.

import (
	"errors"
	"testing"

	"textmachine/platform/internal/pgstore"
)

func TestRefuteTheAbandonRefusalIsExactlyWhatTheTwoPrintedColumnsSay(t *testing.T) {
	for _, tc := range []struct {
		name     string
		sql      string
		wantUnit string
		wantSpnt string
	}{
		{"a unit and a baseline", `update run_attempts set reconcile_failures=5 where run_id=$1`, "named", "a figure"},
		{"no unit, baseline kept (ReleaseSpawnClaim)", `update run_attempts set unit_name=null, reconcile_failures=5 where run_id=$1`, "(none)", "a figure"},
		{"a unit, no baseline (pre-column attempt)", `update run_attempts set spend_baseline_micro_usd=null, reconcile_failures=5 where run_id=$1`, "named", "?"},
		{"neither: never reached the engine", `update run_attempts set unit_name=null, spend_baseline_micro_usd=null, reconcile_failures=5 where run_id=$1`, "(none)", "?"},
	} {
		t.Run(tc.name, func(t *testing.T) {
			f := newFixture(t, "20", 500)
			run, err := f.svc.Start(f.ctx, StartRequest{UserID: "u1", BookID: f.bookID(t), CeilingChapters: 10})
			if err != nil {
				t.Fatal(err)
			}
			if err := f.svc.Spawn(f.ctx, run.ID); err != nil {
				t.Fatal(err)
			}
			if _, err := f.store.Pool().Exec(f.ctx, tc.sql, run.ID); err != nil {
				t.Fatal(err)
			}
			rows, err := f.store.StalledRuns(f.ctx, StalledAfter)
			if err != nil {
				t.Fatal(err)
			}
			if len(rows) != 1 {
				t.Fatalf("%d rows", len(rows))
			}
			unit, spent := "named", "?"
			if rows[0].UnitName == "" {
				unit = "(none)"
			}
			if rows[0].SpentMicroUSD != nil {
				spent = "a figure"
			}
			if unit != tc.wantUnit || spent != tc.wantSpnt {
				t.Fatalf("the table printed UNIT=%s SPENT=%s, want UNIT=%s SPENT=%s", unit, spent, tc.wantUnit, tc.wantSpnt)
			}
			err = f.store.AbandonRun(f.ctx, run.ID, "the host is gone", false, f.now)
			refused := errors.Is(err, pgstore.ErrRunMayHaveAProcess)
			// This is what the runbook tells the operator to read off the two columns.
			predicted := unit != "(none)" || spent != "?"
			t.Logf("UNIT=%-6s SPENT=%-8s -> runbook predicts refusal=%v, handle refused=%v (%v)",
				unit, spent, predicted, refused, err)
			if predicted != refused {
				t.Fatalf("NOT REFUTED: the table said %v and the handle did %v", predicted, refused)
			}
		})
	}
}
