package deterministic

import (
	"context"
	"fmt"

	"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/org"
	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/prompt"
	"github.com/flothus/tmux-xterm-research/server-go/internal/harness/store"
)

// delegate creates a task, assigns it to a single worker, and sends a
// delegate envelope — all atomically. Moved from orchestrator/master.go
// as part of LM-B. The "broken handoff chain" failure mode (task
// in_progress but no worker ever notified, because the message send
// failed after the task UPDATE) is impossible here: every write
// commits together or none does.
//
// Worker selection: first declared delegate target of master in the org
// yaml. Future deterministic-policy hooks (load balancing, latency
// scoring) would slot in here.
func (c *Coordinator) delegate(ctx context.Context, userText string, res prompt.Result) error {
	kind := res.Kind
	worker := "worker-1"
	masterAgentID := "master"
	if c.Runner != nil {
		if id, ok := c.Runner.AgentIDFor(c.MasterRole); ok {
			masterAgentID = id
		}
		if targetRole, ok := c.Runner.DelegateTarget(c.MasterRole); ok {
			if _, err := c.Runner.SpawnRole(ctx, c.RunID, targetRole); err != nil {
				return fmt.Errorf("deterministic: lazy-spawn worker %s: %w", targetRole, err)
			}
			if id, ok := c.Runner.AgentIDFor(targetRole); ok {
				worker = id
			}
		}
	}

	taskID := genID("task")
	promptID := genID("prompt")
	now := store.FmtTime(store.Now())
	title := trunc(userText, 80)

	msg := &envelope.Envelope{
		ID: genID("msg"), RunID: c.RunID,
		From: masterAgentID, To: worker, Type: envelope.TypeDelegate,
		TaskID: taskID, TTLMs: 60000,
		Payload: envelope.Payload{Intent: userText, Expects: envelope.ExpectsReport},
	}

	var rootWon bool
	err := c.Orch.St.Tx(ctx, func(q store.Querier) error {
		if _, err := q.Exec(
			`INSERT INTO prompts(id, run_id, source, text, classified_kind, classified_at,
			                     classified_confidence, classified_rationale, classifier_impl)
			 VALUES(?, ?, 'user', ?, ?, ?, ?, ?, ?)`,
			promptID, c.RunID, userText, string(kind), now,
			res.Confidence, res.Rationale, prompt.ImplName(c.Classer),
		); err != nil {
			return err
		}
		if _, err := q.Exec(
			`INSERT INTO tasks(id, run_id, owner_agent_id, prompt_id, title, state, attempts, created_at, updated_at)
			 VALUES(?, ?, ?, ?, ?, 'created', 0, ?, ?)`,
			taskID, c.RunID, worker, promptID, title, now, now,
		); err != nil {
			return err
		}
		if _, err := q.Exec(`UPDATE tasks SET state='assigned', updated_at=? WHERE id=?`, now, taskID); err != nil {
			return err
		}
		if _, err := q.Exec(`UPDATE tasks SET state='in_progress', updated_at=? WHERE id=?`, now, taskID); err != nil {
			return err
		}
		r, err := q.Exec(`UPDATE runs SET root_task_id=? WHERE id=? AND root_task_id IS NULL`, taskID, c.RunID)
		if err != nil {
			return err
		}
		n, _ := r.RowsAffected()
		rootWon = n == 1
		if err := c.Queue.SendInTx(q, msg); err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return fmt.Errorf("deterministic: atomic delegate: %w", err)
	}

	_, _ = c.Orch.Bus.Emit(ctx, event.Event{
		Kind: event.KindTaskCreated, RunID: c.RunID, TaskID: taskID,
		Payload: map[string]any{"title": title, "parent": ""},
	})
	_, _ = c.Orch.Bus.Emit(ctx, event.Event{
		Kind: event.KindTaskAssigned, RunID: c.RunID, TaskID: taskID, AgentID: worker,
	})
	_, _ = c.Orch.Bus.Emit(ctx, event.Event{
		Kind: event.KindTaskStateChanged, RunID: c.RunID, TaskID: taskID,
		Payload: map[string]any{"from": "created", "to": "assigned"},
	})
	_, _ = c.Orch.Bus.Emit(ctx, event.Event{
		Kind: event.KindTaskStateChanged, RunID: c.RunID, TaskID: taskID,
		Payload: map[string]any{"from": "assigned", "to": "in_progress"},
	})
	if rootWon {
		_, _ = c.Orch.Bus.Emit(ctx, event.Event{
			Kind: "run.root_set", RunID: c.RunID, TaskID: taskID,
		})
	}
	c.Queue.EmitSent(ctx, msg)
	return nil
}

// fanOut sends userText as a Delegate envelope to EVERY declared child
// of master (plus emits master's structural self_report directly).
// Used for org-wide introspection prompts in deterministic mode.
func (c *Coordinator) fanOut(ctx context.Context, userText string, res prompt.Result) error {
	if c.Runner == nil {
		return fmt.Errorf("deterministic: fanOut requires Runner (no org loaded)")
	}
	targets := c.Runner.DelegateTargets(c.MasterRole)
	if len(targets) == 0 {
		return fmt.Errorf("deterministic: %s has no delegates_to to fan out to", c.MasterRole)
	}
	masterAgentID := "master"
	if id, ok := c.Runner.AgentIDFor(c.MasterRole); ok {
		masterAgentID = id
	}

	rootTaskID := genID("task")
	promptID := genID("prompt")
	now := store.FmtTime(store.Now())
	title := "introspect: " + trunc(userText, 80)
	if err := c.Orch.St.Tx(ctx, func(q store.Querier) error {
		if _, err := q.Exec(
			`INSERT INTO prompts(id, run_id, source, text, classified_kind, classified_at)
			 VALUES(?, ?, 'user', ?, ?, ?)`,
			promptID, c.RunID, userText, string(res.Kind), now,
		); err != nil {
			return err
		}
		if _, err := q.Exec(
			`INSERT INTO tasks(id, run_id, owner_agent_id, prompt_id, title, state, attempts, created_at, updated_at)
			 VALUES(?, ?, ?, ?, ?, 'created', 0, ?, ?)`,
			rootTaskID, c.RunID, masterAgentID, promptID, title, now, now,
		); err != nil {
			return err
		}
		if _, err := q.Exec(`UPDATE tasks SET state='in_progress', updated_at=? WHERE id=?`, now, rootTaskID); err != nil {
			return err
		}
		if _, err := q.Exec(`UPDATE runs SET root_task_id=? WHERE id=? AND root_task_id IS NULL`, rootTaskID, c.RunID); err != nil {
			return err
		}
		return nil
	}); err != nil {
		return fmt.Errorf("deterministic: fanOut root task: %w", err)
	}

	// Structural self_report for master (no LLM turn needed — identity is known).
	if c.Runner.Def != nil {
		var masterRole *org.Role
		for i := range c.Runner.Def.Roles {
			if c.Runner.Def.Roles[i].ID == c.MasterRole {
				masterRole = &c.Runner.Def.Roles[i]
				break
			}
		}
		if masterRole != nil {
			subOrgs := org.SubOrgByRole(c.Runner.Def)
			_, _ = c.Orch.Bus.Emit(ctx, event.Event{
				Kind: "agent.self_report", RunID: c.RunID, AgentID: masterAgentID, TaskID: rootTaskID,
				Payload: map[string]any{
					"agent_id":     masterAgentID,
					"role":         masterRole.ID,
					"provider":     masterRole.Provider,
					"sub_org":      subOrgs[masterRole.ID],
					"is_connector": org.IsConnectorRole(*masterRole),
					"tools":        masterRole.Tools,
					"task_id":      rootTaskID,
					"notes":        "structural self-report emitted by deterministic.Coordinator.fanOut",
					"ts":           store.FmtTime(store.Now()),
					"source":       "orchestrator",
				},
			})
		}
	}

	for _, target := range targets {
		if _, err := c.Runner.SpawnRole(ctx, c.RunID, target); err != nil {
			_, _ = c.Orch.Bus.Emit(ctx, event.Event{
				Kind: "master.fan_out.spawn_failed", RunID: c.RunID,
				Payload: map[string]any{"target_role": target, "error": err.Error()},
			})
			continue
		}
		recipient := target
		if id, ok := c.Runner.AgentIDFor(target); ok {
			recipient = id
		}
		childTaskID := genID("task")
		if err := c.Orch.St.Tx(ctx, func(q store.Querier) error {
			_, err := q.Exec(
				`INSERT INTO tasks(id, run_id, parent_task_id, owner_agent_id, prompt_id, title, state, attempts, created_at, updated_at)
				 VALUES(?, ?, ?, ?, ?, ?, 'in_progress', 0, ?, ?)`,
				childTaskID, c.RunID, rootTaskID, recipient, promptID,
				"introspect("+target+")", now, now,
			)
			return err
		}); err != nil {
			_, _ = c.Orch.Bus.Emit(ctx, event.Event{
				Kind: "master.fan_out.task_create_failed", RunID: c.RunID,
				Payload: map[string]any{"target_role": target, "error": err.Error()},
			})
			continue
		}
		env := &envelope.Envelope{
			ID:    genID("msg"),
			RunID: c.RunID, From: masterAgentID, To: recipient,
			Type: envelope.TypeDelegate, TaskID: childTaskID, TTLMs: 60000,
			Payload: envelope.Payload{Intent: userText, Expects: envelope.ExpectsReport},
		}
		if err := c.Queue.Send(ctx, env); err != nil {
			_, _ = c.Orch.Bus.Emit(ctx, event.Event{
				Kind: "master.fan_out.send_failed", RunID: c.RunID, TaskID: childTaskID,
				Payload: map[string]any{"target_role": target, "to": recipient, "error": err.Error()},
			})
			continue
		}
		_, _ = c.Orch.Bus.Emit(ctx, event.Event{
			Kind: "master.fan_out.dispatched", RunID: c.RunID, TaskID: childTaskID,
			Payload: map[string]any{"target_role": target, "to": recipient, "from": masterAgentID},
		})
	}
	return nil
}

// steer redirects an in-flight task by sending a Steering envelope to
// its owner.
func (c *Coordinator) steer(ctx context.Context, targetTaskID, userText string) error {
	if targetTaskID == "" {
		return fmt.Errorf("deterministic: steering requires target task")
	}
	var owner string
	if err := c.Orch.St.DB().QueryRowContext(ctx,
		`SELECT IFNULL(owner_agent_id,'') FROM tasks WHERE id=?`, targetTaskID,
	).Scan(&owner); err != nil {
		return err
	}
	if owner == "" {
		return fmt.Errorf("deterministic: task %s has no owner", targetTaskID)
	}
	msg := &envelope.Envelope{
		ID: genID("msg"), RunID: c.RunID,
		From: "master", To: owner, Type: envelope.TypeSteering,
		TaskID: targetTaskID, TTLMs: 10000,
		Payload: envelope.Payload{Intent: userText, Expects: envelope.ExpectsAck},
	}
	err := c.Orch.St.Tx(ctx, func(q store.Querier) error {
		if _, err := q.Exec(
			`INSERT INTO steering(id, target_task_id, forwarded_to, created_at) VALUES(?, ?, ?, ?)`,
			genID("st"), targetTaskID, owner, store.FmtTime(store.Now()),
		); err != nil {
			return err
		}
		return c.Queue.SendInTx(q, msg)
	})
	if err != nil {
		return fmt.Errorf("deterministic: atomic steer: %w", err)
	}
	c.Queue.EmitSent(ctx, msg)
	_, _ = c.Orch.Bus.Emit(ctx, event.Event{
		Kind:    "master.steered",
		RunID:   c.RunID,
		TaskID:  targetTaskID,
		Payload: map[string]any{"to": owner, "intent": userText},
	})
	return nil
}

// findReuseCandidate looks for an in-flight task whose title shares ≥2
// significant keywords with userText. Returns the task id or "".
func (c *Coordinator) findReuseCandidate(ctx context.Context, userText string) string {
	rows, err := c.Orch.St.DB().QueryContext(ctx,
		`SELECT id, title FROM tasks
		   WHERE run_id=? AND state IN ('in_progress','awaiting_clarification','awaiting_handoff','awaiting_subtask')
		   ORDER BY created_at DESC LIMIT 20`,
		c.RunID,
	)
	if err != nil {
		return ""
	}
	defer rows.Close()
	newToks := tokenize(userText)
	if len(newToks) == 0 {
		return ""
	}
	for rows.Next() {
		var id, title string
		_ = rows.Scan(&id, &title)
		titleToks := tokenize(title)
		overlap := 0
		for t := range newToks {
			if titleToks[t] {
				overlap++
				if overlap >= 2 {
					return id
				}
			}
		}
	}
	return ""
}

// tokenize lowercases s and returns alphanum tokens of length ≥4 as a
// set.
func tokenize(s string) map[string]bool {
	out := map[string]bool{}
	var cur []rune
	for _, r := range s {
		switch {
		case r >= 'A' && r <= 'Z':
			cur = append(cur, r+32)
		case (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9'):
			cur = append(cur, r)
		default:
			if len(cur) >= 4 {
				out[string(cur)] = true
			}
			cur = cur[:0]
		}
	}
	if len(cur) >= 4 {
		out[string(cur)] = true
	}
	return out
}
