package orchestrator_test

import (
	"context"
	"path/filepath"
	"testing"
	"time"

	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/envelope"
	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/event"
	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/orchestrator"
	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/store"
	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/transport"
)

// TestMasterRecoversFromDeadLetter is the regression test for violation C3.
// A dead-lettered delegate must reach the master and trigger:
//   - the affected task transitions to Failed (not silently stuck in_progress)
//   - a user_notifications row records the failure
//   - the event log carries master.nack_received with recovery metadata
//
// Today the master's TypeNack handler emits an event and acks — the task
// stays in_progress, the user learns nothing. This test fails on that code.
func TestMasterRecoversFromDeadLetter(t *testing.T) {
	tmp := t.TempDir()
	st, err := store.Open(filepath.Join(tmp, "harness.db"))
	if err != nil {
		t.Fatal(err)
	}
	defer st.Close()
	bus := event.NewBus(st)
	q := transport.New(st, bus)
	orch := orchestrator.New(st, bus)
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	_ = orch.CreateRun(ctx, "run-nack", "nack recovery smoke")
	for _, id := range []string{"master", "worker-1"} {
		_, _ = st.DB().Exec(`INSERT INTO agents(id, run_id, status, spawned_at, heartbeat_at) VALUES(?,'run-nack','running',?,?)`,
			id, store.FmtTime(store.Now()), store.FmtTime(store.Now()))
	}

	now := store.FmtTime(store.Now())
	// Set up a task in 'in_progress' owned by worker-1, root of the run.
	_, _ = st.DB().Exec(`INSERT INTO tasks(id, run_id, owner_agent_id, title, state, attempts, created_at, updated_at) VALUES('t-dead','run-nack','worker-1','test task','in_progress',0,?,?)`, now, now)
	_, _ = st.DB().Exec(`UPDATE runs SET root_task_id='t-dead' WHERE id='run-nack'`)

	// Simulate the queue's dead-letter notice arriving at master.
	deadLetter := &envelope.Envelope{
		ID: "dl_xyz", RunID: "run-nack",
		From: "queue", To: "master", Type: envelope.TypeNack,
		TaskID: "t-dead", TTLMs: 60000,
		Payload: envelope.Payload{Reason: "worker crashed: panic in send_message"},
	}
	if err := q.Send(ctx, deadLetter); err != nil {
		t.Fatalf("send dead letter: %v", err)
	}

	master := orchestrator.NewMaster(orch, q, nil, "run-nack")
	handled, err := master.ProcessInbox(ctx)
	if err != nil {
		t.Fatalf("ProcessInbox: %v", err)
	}
	if !handled {
		t.Fatalf("ProcessInbox returned handled=false; expected to handle the dead-letter envelope")
	}

	// 1. The task must be in Failed.
	var state string
	_ = st.DB().QueryRow(`SELECT state FROM tasks WHERE id='t-dead'`).Scan(&state)
	if state != "failed" {
		t.Errorf("task state after dead-letter = %q, want \"failed\" — handoff chain still broken", state)
	}
	// 2. A user_notifications row must exist for the failure.
	var notifs int
	_ = st.DB().QueryRow(`SELECT COUNT(*) FROM user_notifications WHERE run_id='run-nack' AND kind='delegate_failed'`).Scan(&notifs)
	if notifs < 1 {
		t.Errorf("no user_notifications row of kind=delegate_failed — user not informed of failure")
	}
}
