/**
 * AgentRepository - CRUD + list_active, update_heartbeat for agents table (§10)
 *
 * Implements Repository<AgentRecord, AgentInsert, AgentUpdate> per contracts §6.
 *
 * @module packages/runtime/src/storage/repositories/AgentRepository
 */

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

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

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

export type AgentStatus = 'starting' | 'running' | 'completed' | 'failed' | 'lost' | 'cancelled'
export type AgentType = 'executor' | 'reviewer' | 'debugger' | 'compactor' | 'experience_miner'

export interface AgentRecord {
  id: AgentID
  session_id: SessionID
  type: AgentType
  status: AgentStatus
  pid?: number
  task_id?: TaskID
  model_provider_id?: string
  model_id?: string
  started_at: ISOTimeString
  completed_at?: ISOTimeString
  last_heartbeat_at?: ISOTimeString
  metadata_json?: string
}

export type AgentInsert = Omit<AgentRecord, 'id' | 'status'> & {
  id?: AgentID
}

export type AgentUpdate = Partial<Omit<AgentRecord, 'id' | 'session_id' | 'started_at' | 'status'>>

// =============================================================================
// AgentRepository
// =============================================================================

export class AgentRepository implements Repository<AgentRecord, AgentInsert, AgentUpdate> {
  private db: DatabaseHandle

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

  /**
   * Get an agent by ID.
   */
  async get(id: AgentID, tx?: TransactionHandle): Promise<AgentRecord | undefined> {
    const stmt = (tx?.db ?? this.db).prepare('SELECT * FROM agents WHERE id = ?')
    const row = stmt.get(id) as AgentRecord | undefined
    return row
  }

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

    const stmt = (tx?.db ?? this.db).prepare(`
      INSERT INTO agents (
        id, session_id, type, status,
        pid, task_id,
        model_provider_id, model_id,
        started_at, completed_at, last_heartbeat_at,
        metadata_json
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `)

    stmt.run(
      record.id,
      record.session_id,
      record.type,
      status,
      record.pid ?? null,
      record.task_id ?? null,
      record.model_provider_id ?? null,
      record.model_id ?? null,
      record.started_at,
      record.completed_at ?? null,
      record.last_heartbeat_at ?? null,
      record.metadata_json ?? null,
    )
  }

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

    // NOTE: status is NOT updatable here - only EventStore.project() writes status columns
    if (patch.pid !== undefined) {
      fields.push('pid = ?')
      values.push(patch.pid)
    }
    if (patch.task_id !== undefined) {
      fields.push('task_id = ?')
      values.push(patch.task_id)
    }
    if (patch.model_provider_id !== undefined) {
      fields.push('model_provider_id = ?')
      values.push(patch.model_provider_id)
    }
    if (patch.model_id !== undefined) {
      fields.push('model_id = ?')
      values.push(patch.model_id)
    }
    if (patch.completed_at !== undefined) {
      fields.push('completed_at = ?')
      values.push(patch.completed_at)
    }
    if (patch.last_heartbeat_at !== undefined) {
      fields.push('last_heartbeat_at = ?')
      values.push(patch.last_heartbeat_at)
    }
    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 agents SET ${fields.join(', ')} WHERE id = ?`)
    stmt.run(...values)
  }

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

  /**
   * List active (running or starting) agents for a session.
   */
  async list_active(session_id: SessionID): Promise<AgentRecord[]> {
    const stmt = this.db.prepare(
      'SELECT * FROM agents WHERE session_id = ? AND status IN (?, ?) ORDER BY started_at DESC',
    )
    return stmt.all(session_id, 'running', 'starting') as AgentRecord[]
  }

  /**
   * Update heartbeat timestamp for an agent.
   * This is an INV-1 exemption - allows direct status column update.
   */
  async update_heartbeat(id: AgentID, heartbeat_at: ISOTimeString): Promise<void> {
    const stmt = this.db.prepare('UPDATE agents SET last_heartbeat_at = ? WHERE id = ?')
    stmt.run(heartbeat_at, id)
  }
}