// v2.x: Background subagent task service.
// Creates and tracks background tasks that run as independent inference
// sessions. The spawner creates a session+chat, inserts messages, and
// dispatches inference asynchronously. Callers poll status and retrieve
// results via the companion tools (background-subagent-tools.ts).
//
// Module-level inference enqueuer: set at server startup so tools can
// dispatch background inference without importing the runner directly.

import type { Sql } from '../db.js';
import type { FastifyBaseLogger } from 'fastify';

export interface BackgroundTask {
  id: string;
  session_id: string;
  chat_id: string;
  agent: string | null;
  model: string;
  input: string;
  status: 'pending' | 'running' | 'completed' | 'failed' | 'cancelled';
  output_summary: string | null;
  created_at: string;
  finished_at: string | null;
}

// Module-level reference to the inference enqueuer, set at server startup.
let _enqueueInference:
  | ((sessionId: string, chatId: string, assistantMessageId: string, user: string) => void)
  | null = null;

export function setBackgroundInferenceEnqueuer(
  enqueue: (
    sessionId: string,
    chatId: string,
    assistantMessageId: string,
    user: string,
  ) => void,
): void {
  _enqueueInference = enqueue;
}

function mapTaskState(state: string): BackgroundTask['status'] {
  switch (state) {
    case 'pending':
      return 'pending';
    case 'running':
      return 'running';
    case 'completed':
      return 'completed';
    case 'failed':
      return 'failed';
    case 'blocked':
      return 'pending'; // blocked is internal — surface as pending
    case 'cancelled':
      return 'cancelled';
    default:
      return 'pending';
  }
}

// Spawn a background subagent task: create session + chat + messages + tasks
// row, then fire-and-forget the inference. Returns immediately with the task
// metadata — inference runs asynchronously.
export async function spawnBackgroundTask(
  sql: Sql,
  log: FastifyBaseLogger,
  projectId: string,
  input: string,
  model: string,
  agent?: string,
  label?: string,
): Promise<BackgroundTask> {
  const sessionName =
    label != null && label.length > 0
      ? `Subagent: ${label}`
      : `Background: ${input.slice(0, 50)}${input.length > 50 ? '...' : ''}`;

  const result = await sql.begin(async (tx) => {
    // 1. Create session for the background task
    const [sess] = await tx<{ id: string }[]>`
      INSERT INTO sessions (project_id, name, model, system_prompt)
      VALUES (${projectId}, ${sessionName}, ${model}, '')
      RETURNING id
    `;
    const sessionId = sess!.id;

    // 2. Create chat in that session
    const [ch] = await tx<{ id: string }[]>`
      INSERT INTO chats (session_id, name, status)
      VALUES (${sessionId}, ${label ?? null}, 'open')
      RETURNING id
    `;
    const chatId = ch!.id;

    // 3. Insert user message with the task input
    await tx`
      INSERT INTO messages (session_id, chat_id, role, content, status, created_at)
      VALUES (${sessionId}, ${chatId}, 'user', ${input}, 'complete', clock_timestamp())
    `;

    // 4. Insert streaming assistant message (inference fills it)
    const [assistantRow] = await tx<{ id: string }[]>`
      INSERT INTO messages (session_id, chat_id, role, content, status, created_at)
      VALUES (${sessionId}, ${chatId}, 'assistant', '', 'streaming', clock_timestamp())
      RETURNING id
    `;
    const assistantMessageId = assistantRow!.id;

    // 5. Insert tasks row for tracking
    const [task] = await tx<{ id: string; created_at: string }[]>`
      INSERT INTO tasks (project_id, session_id, state, input, agent, model)
      VALUES (${projectId}, ${sessionId}, 'running', ${input}, ${agent ?? null}, ${model})
      RETURNING id, created_at
    `;

    return { sessionId, chatId, assistantMessageId, task: task! };
  });

  // After the transaction commits, fire-and-forget inference dispatch.
  if (_enqueueInference) {
    try {
      _enqueueInference(result.sessionId, result.chatId, result.assistantMessageId, 'default');
    } catch (err) {
      log.warn(
        { err, taskId: result.task.id },
        'background inference enqueue failed',
      );
    }
  }

  log.info(
    {
      taskId: result.task.id,
      sessionId: result.sessionId,
      chatId: result.chatId,
      model,
      agent,
    },
    'spawned background subagent task',
  );

  return {
    id: result.task.id,
    session_id: result.sessionId,
    chat_id: result.chatId,
    agent: agent ?? null,
    model,
    input,
    status: 'running',
    output_summary: null,
    created_at: result.task.created_at,
    finished_at: null,
  };
}

// Look up a background task by its tasks.id. Includes the status from the
// tasks table and the chat_id from the linked chat.
export async function getBackgroundTaskStatus(
  sql: Sql,
  taskId: string,
): Promise<BackgroundTask | null> {
  const rows = await sql<
    {
      id: string;
      session_id: string;
      state: string;
      input: string;
      agent: string | null;
      model: string | null;
      output_summary: string | null;
      created_at: string;
      ended_at: string | null;
    }[]
  >`
    SELECT id, session_id, state, input, agent, model, output_summary, created_at, ended_at
    FROM tasks
    WHERE id = ${taskId}
  `;
  if (rows.length === 0) return null;
  const r = rows[0]!;

  // Find the chat_id from the session (background sessions have exactly one chat).
  const chatRows = await sql<{ id: string }[]>`
    SELECT id FROM chats WHERE session_id = ${r.session_id} LIMIT 2
  `;

  return {
    id: r.id,
    session_id: r.session_id,
    chat_id: chatRows[0]?.id ?? '',
    agent: r.agent,
    model: r.model ?? '',
    input: r.input,
    status: mapTaskState(r.state),
    output_summary: r.output_summary,
    created_at: r.created_at,
    finished_at: r.ended_at,
  };
}

// Retrieve the full output and token usage from a completed background task.
// Returns null if the task has no completed assistant message.
export async function getBackgroundTaskResult(
  sql: Sql,
  taskId: string,
  chatId: string,
): Promise<{
  output: string;
  token_usage: { prompt: number; completion: number } | null;
} | null> {
  // Verify the task exists and chatId belongs to it.
  const taskRows = await sql<{ session_id: string }[]>`
    SELECT session_id FROM tasks WHERE id = ${taskId}
  `;
  if (taskRows.length === 0) return null;

  // Read the last complete assistant message (the one with content).
  const msgRows = await sql<
    {
      content: string;
      tokens_used: number | null;
      ctx_used: number | null;
    }[]
  >`
    SELECT content, tokens_used, ctx_used
    FROM messages
    WHERE chat_id = ${chatId}
      AND role = 'assistant'
      AND status = 'complete'
      AND content <> ''
    ORDER BY created_at DESC
    LIMIT 1
  `;
  if (msgRows.length === 0) return null;

  const m = msgRows[0]!;
  return {
    output: m.content,
    token_usage:
      m.tokens_used != null || m.ctx_used != null
        ? { prompt: m.ctx_used ?? 0, completion: m.tokens_used ?? 0 }
        : null,
  };
}

// Cancel a pending or running background task. Returns true if a row was
// actually updated (the task existed and was in a cancellable state).
export async function cancelBackgroundTask(
  sql: Sql,
  taskId: string,
): Promise<boolean> {
  const rows = await sql<{ id: string }[]>`
    UPDATE tasks
    SET state = 'cancelled', ended_at = clock_timestamp()
    WHERE id = ${taskId}
      AND state IN ('pending', 'running')
    RETURNING id
  `;
  return rows.length > 0;
}
