/**
 * ToolRunRepository - CRUD + list_by_task, list_by_origin_message for tool_runs table (§11)
 *
 * Implements Repository<ToolRunRecord, ToolRunInsert, ToolRunUpdate> per contracts §6.
 *
 * @module packages/runtime/src/storage/repositories/ToolRunRepository
 */

import type {
  Repository,
  TransactionHandle,
  SessionID,
  TaskID,
  AgentID,
  ToolRunID,
  MessageID,
  ISOTimeString,
} from '@aircoding/contracts'

import { DatabaseHandle } from '../MigrationRunner.js'

// =============================================================================
// Types - per db-schema §11
// =============================================================================

export type ToolRunStatus = 'running' | 'ok' | 'error' | 'cancelled'

export interface ToolRunRecord {
  id: ToolRunID
  session_id: SessionID
  task_id?: TaskID
  agent_id?: AgentID
  origin_message_id?: MessageID
  tool_name: string
  status: ToolRunStatus
  input_json: string
  output_json?: string
  error_json?: string
  started_at: ISOTimeString
  completed_at?: ISOTimeString
  duration_ms?: number
  artifacts_json?: string
  evidence_refs_json?: string
  metadata_json?: string
}

export type ToolRunInsert = Omit<ToolRunRecord, 'id' | 'status'> & {
  id?: ToolRunID
}

export type ToolRunUpdate = Partial<Omit<ToolRunRecord, 'id' | 'session_id' | 'tool_name' | 'started_at' | 'status'>>

// =============================================================================
// ToolRunRepository
// =============================================================================

export class ToolRunRepository implements Repository<ToolRunRecord, ToolRunInsert, ToolRunUpdate> {
  private db: DatabaseHandle

  constructor(db: DatabaseHandle) {
    this.db = db
  }

  /**
   * Get a tool run by ID.
   */
  async get(id: ToolRunID, tx?: TransactionHandle): Promise<ToolRunRecord | undefined> {
    const stmt = (tx?.db ?? this.db).prepare('SELECT * FROM tool_runs WHERE id = ?')
    const row = stmt.get(id) as ToolRunRecord | undefined
    return row
  }

  /**
   * Insert a new tool run. Status is set by EventStore projection (INV-1).
   */
  async insert(record: ToolRunInsert, tx?: TransactionHandle): Promise<void> {
    // Status is set by EventStore.project(), not by caller
    const status: ToolRunStatus = 'running'

    const stmt = (tx?.db ?? this.db).prepare(`
      INSERT INTO tool_runs (
        id, session_id, task_id, agent_id, origin_message_id,
        tool_name, status,
        input_json, output_json, error_json,
        started_at, completed_at, duration_ms,
        artifacts_json, evidence_refs_json, metadata_json
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `)

    stmt.run(
      record.id,
      record.session_id,
      record.task_id ?? null,
      record.agent_id ?? null,
      record.origin_message_id ?? null,
      record.tool_name,
      status,
      record.input_json,
      record.output_json ?? null,
      record.error_json ?? null,
      record.started_at,
      record.completed_at ?? null,
      record.duration_ms ?? null,
      record.artifacts_json ?? null,
      record.evidence_refs_json ?? null,
      record.metadata_json ?? null,
    )
  }

  /**
   * Update an existing tool run. Status changes only via EventStore projection (INV-1).
   */
  async update(id: ToolRunID, patch: ToolRunUpdate, tx?: TransactionHandle): Promise<void> {
    const fields: string[] = []
    const values: unknown[] = []

    // NOTE: status is NOT updatable here - only EventStore.project() writes status columns
    if (patch.output_json !== undefined) {
      fields.push('output_json = ?')
      values.push(patch.output_json)
    }
    if (patch.error_json !== undefined) {
      fields.push('error_json = ?')
      values.push(patch.error_json)
    }
    if (patch.completed_at !== undefined) {
      fields.push('completed_at = ?')
      values.push(patch.completed_at)
    }
    if (patch.duration_ms !== undefined) {
      fields.push('duration_ms = ?')
      values.push(patch.duration_ms)
    }
    if (patch.artifacts_json !== undefined) {
      fields.push('artifacts_json = ?')
      values.push(patch.artifacts_json)
    }
    if (patch.evidence_refs_json !== undefined) {
      fields.push('evidence_refs_json = ?')
      values.push(patch.evidence_refs_json)
    }
    if (patch.metadata_json !== undefined) {
      fields.push('metadata_json = ?')
      values.push(patch.metadata_json)
    }

    if (fields.length === 0) {
      return // Nothing to update
    }

    values.push(id)
    const stmt = (tx?.db ?? this.db).prepare(`UPDATE tool_runs SET ${fields.join(', ')} WHERE id = ?`)
    stmt.run(...values)
  }

  // =============================================================================
  // Extra methods
  // =============================================================================

  /**
   * List tool runs for a specific task.
   */
  async list_by_task(task_id: TaskID): Promise<ToolRunRecord[]> {
    const stmt = this.db.prepare(
      'SELECT * FROM tool_runs WHERE task_id = ? ORDER BY started_at DESC',
    )
    return stmt.all(task_id) as ToolRunRecord[]
  }

  /**
   * List tool runs for a specific origin message.
   */
  async list_by_origin_message(message_id: MessageID): Promise<ToolRunRecord[]> {
    const stmt = this.db.prepare(
      'SELECT * FROM tool_runs WHERE origin_message_id = ? ORDER BY started_at ASC',
    )
    return stmt.all(message_id) as ToolRunRecord[]
  }
}