// One-time backfill: walk existing model_call rows and populate the
// outcomes + tool_uses tables so the indexed-query path has historical
// data. Runs asynchronously after daemon startup so the dashboard is
// available immediately; queries see partial data while the backfill
// progresses, then complete data once it finishes.

import type Database from 'better-sqlite3'
import { logger } from '../../core/logger.ts'
import type { AgentId } from '../../core/agent.ts'
import type { ActionId, RunId, ThreadId } from '../../core/ids.ts'
import type { AgentPacket } from '../../core/packet.ts'
import { extractAfterIngest } from './ingest-outcomes.ts'

const BATCH_SIZE = 200

export async function backfillOutcomes(raw: Database.Database): Promise<void> {
  const already = raw.prepare('SELECT COUNT(*) AS n FROM outcomes').get() as { n: number }
  if (already.n > 0) {
    logger.debug(`backfill-outcomes: skipped (${already.n} rows already present)`)
    return
  }
  const total = (raw
    .prepare("SELECT COUNT(*) AS n FROM actions WHERE kind = 'model_call' OR kind = 'mcp_call'")
    .get() as { n: number }).n
  if (total === 0) {
    logger.debug('backfill-outcomes: skipped (no actions to extract)')
    return
  }

  logger.info(`backfill-outcomes: scanning ${total.toLocaleString()} actions…`)
  const t0 = performance.now()
  const select = raw.prepare(
    `SELECT a.id, a.run_id, a.thread_id, a.kind, a.source_agent, a.ts, a.dur_ms, a.cost_usd, a.tokens_in, a.tokens_out, a.cache_read_tokens, a.cache_write_tokens, p.payload, a.parent_action_id
     FROM actions a
     LEFT JOIN action_payloads p ON p.action_id = a.id
     WHERE a.kind IN ('model_call', 'mcp_call')
     ORDER BY a.ts ASC
     LIMIT ? OFFSET ?`,
  )

  let processed = 0
  let offset = 0
  while (true) {
    const rows = select.all(BATCH_SIZE, offset) as Array<{
      id: string
      run_id: string
      thread_id: string
      kind: string
      source_agent: string
      ts: number
      dur_ms: number
      cost_usd: number | null
      tokens_in: number | null
      tokens_out: number | null
      cache_read_tokens: number | null
      cache_write_tokens: number | null
      payload: string | null
      parent_action_id: string | null
    }>
    if (rows.length === 0) break

    raw.transaction(() => {
      for (const row of rows) {
        if (row.payload == null) continue
        let parsed: unknown = null
        try {
          parsed = JSON.parse(row.payload)
        } catch {
          continue
        }
        if (!parsed || typeof parsed !== 'object') continue
        const packet: AgentPacket = {
          id: row.id as ActionId,
          runId: row.run_id as RunId,
          threadId: row.thread_id as ThreadId,
          ts: row.ts,
          durMs: row.dur_ms,
          sourceAgent: row.source_agent as AgentId,
          parentActionId: (row.parent_action_id as ActionId | null) ?? undefined,
          // biome-ignore lint/suspicious/noExplicitAny: backfill payload type-check is the JSON.parse above
          payload: parsed as any,
          cost: {
            usd: row.cost_usd != null ? row.cost_usd / 1_000_000 : 0,
            tokensIn: row.tokens_in ?? 0,
            tokensOut: row.tokens_out ?? 0,
            tokensCacheRead: row.cache_read_tokens ?? undefined,
            tokensCacheWrite: row.cache_write_tokens ?? undefined,
          },
        }
        extractAfterIngest(raw, packet)
      }
    })()

    processed += rows.length
    offset += rows.length

    if (processed % 2000 === 0 || rows.length < BATCH_SIZE) {
      logger.info(
        `backfill-outcomes: ${processed.toLocaleString()} / ${total.toLocaleString()} (${Math.round((processed / total) * 100)}%)`,
      )
    }

    // Yield to the event loop so this doesn't starve incoming requests.
    await new Promise((resolve) => setImmediate(resolve))
  }

  const outcomesCount = (raw.prepare('SELECT COUNT(*) AS n FROM outcomes').get() as { n: number }).n
  const toolUsesCount = (raw.prepare('SELECT COUNT(*) AS n FROM tool_uses').get() as { n: number }).n
  logger.info(
    `backfill-outcomes: done in ${((performance.now() - t0) / 1000).toFixed(1)}s — ${outcomesCount.toLocaleString()} outcomes, ${toolUsesCount.toLocaleString()} tool_uses`,
  )
}
