/**
 * CommandRunRepository - CRUD + list_by_task + derive_command_status for command_runs table (§12)
 *
 * Implements Repository<CommandRunRecord, CommandRunInsert, CommandRunUpdate> per contracts §6.
 * Includes derive_command_status per DD §4.4.
 *
 * @module packages/runtime/src/storage/repositories/CommandRunRepository
 */

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

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

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

export interface CommandRunRecord {
  id: CommandRunID
  session_id: SessionID
  task_id?: TaskID
  agent_id?: AgentID
  origin_message_id?: MessageID
  tool_run_id?: ToolRunID
  command: string
  cwd: string
  exit_code?: number
  stdout_artifact_id?: ArtifactID
  stderr_artifact_id?: ArtifactID
  combined_artifact_id?: ArtifactID
  started_at: ISOTimeString
  completed_at?: ISOTimeString
  duration_ms?: number
  parsed_diagnostics_json?: string
  metadata_json?: string
}

// Derived status per DD §4.4
export type CommandRunStatus = 'running' | 'ok' | 'error' | 'cancelled' | 'unknown'

export type CommandRunInsert = Omit<CommandRunRecord, 'id'> & {
  id?: CommandRunID
}

export type CommandRunUpdate = Partial<Omit<CommandRunRecord, 'id' | 'session_id' | 'command' | 'cwd' | 'started_at'>>

// =============================================================================
// derive_command_status per DD §4.4
// =============================================================================

/**
 * Derives the command run status per DD §4.4:
 *   completed_at == null            -> "running"
 *   cancellation metadata present   -> "cancelled"
 *   exit_code === 0                 -> "ok"
 *   exit_code != 0 (non-null)       -> "error"
 *   otherwise                       -> "unknown"
 */
export function derive_command_status(fields: {
  completed_at: ISOTimeString | null | undefined
  exit_code: number | null | undefined
  cancelled: boolean
}): CommandRunStatus {
  if (fields.completed_at == null) return 'running'
  if (fields.cancelled) return 'cancelled'
  if (fields.exit_code === 0) return 'ok'
  if (fields.exit_code != null) return 'error'
  return 'unknown'
}

// =============================================================================
// CommandRunRepository
// =============================================================================

export class CommandRunRepository implements Repository<CommandRunRecord, CommandRunInsert, CommandRunUpdate> {
  private db: DatabaseHandle

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

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

  /**
   * Insert a new command run.
   */
  async insert(record: CommandRunInsert, tx?: TransactionHandle): Promise<void> {
    const stmt = (tx?.db ?? this.db).prepare(`
      INSERT INTO command_runs (
        id, session_id, task_id, agent_id, origin_message_id, tool_run_id,
        command, cwd,
        exit_code,
        stdout_artifact_id, stderr_artifact_id, combined_artifact_id,
        started_at, completed_at, duration_ms,
        parsed_diagnostics_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_run_id ?? null,
      record.command,
      record.cwd,
      record.exit_code ?? null,
      record.stdout_artifact_id ?? null,
      record.stderr_artifact_id ?? null,
      record.combined_artifact_id ?? null,
      record.started_at,
      record.completed_at ?? null,
      record.duration_ms ?? null,
      record.parsed_diagnostics_json ?? null,
      record.metadata_json ?? null,
    )
  }

  /**
   * Update an existing command run.
   */
  async update(id: CommandRunID, patch: CommandRunUpdate, tx?: TransactionHandle): Promise<void> {
    const fields: string[] = []
    const values: unknown[] = []

    if (patch.exit_code !== undefined) {
      fields.push('exit_code = ?')
      values.push(patch.exit_code)
    }
    if (patch.stdout_artifact_id !== undefined) {
      fields.push('stdout_artifact_id = ?')
      values.push(patch.stdout_artifact_id)
    }
    if (patch.stderr_artifact_id !== undefined) {
      fields.push('stderr_artifact_id = ?')
      values.push(patch.stderr_artifact_id)
    }
    if (patch.combined_artifact_id !== undefined) {
      fields.push('combined_artifact_id = ?')
      values.push(patch.combined_artifact_id)
    }
    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.parsed_diagnostics_json !== undefined) {
      fields.push('parsed_diagnostics_json = ?')
      values.push(patch.parsed_diagnostics_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 command_runs SET ${fields.join(', ')} WHERE id = ?`)
    stmt.run(...values)
  }

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

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

  /**
   * Get command run with derived status.
   */
  async get_with_derived_status(id: CommandRunID): Promise<{ record: CommandRunRecord | undefined; status: CommandRunStatus }> {
    const record = await this.get(id)
    if (!record) {
      return { record: undefined, status: 'unknown' }
    }

    // Check for cancellation via metadata
    let cancelled = false
    if (record.metadata_json) {
      try {
        const metadata = JSON.parse(record.metadata_json)
        cancelled = metadata.cancelled === true
      } catch {
        // Ignore parse errors
      }
    }

    const status = derive_command_status({
      completed_at: record.completed_at,
      exit_code: record.exit_code,
      cancelled,
    })

    return { record, status }
  }
}