/**
 * TaskAttemptRepository - CRUD + next_attempt_index, list_by_task for task_attempts table (§9)
 *
 * Implements Repository<TaskAttemptRecord, TaskAttemptInsert, TaskAttemptUpdate>
 * per contracts §6.
 *
 * @module packages/runtime/src/storage/repositories/TaskAttemptRepository
 */

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

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

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

export type TaskAttemptStatus = 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'

export interface TaskAttemptRecord {
  id: UUID
  session_id: SessionID
  task_id: TaskID
  attempt_index: number
  agent_id?: AgentID
  status: TaskAttemptStatus
  failure_signature?: string
  failure_summary?: string
  started_at: ISOTimeString
  completed_at?: ISOTimeString
  worker_result_json?: string
  metadata_json?: string
}

export type TaskAttemptInsert = Omit<TaskAttemptRecord, 'id' | 'status'> & {
  id?: UUID
}

export type TaskAttemptUpdate = Partial<Omit<TaskAttemptRecord, 'id' | 'session_id' | 'task_id' | 'attempt_index' | 'started_at' | 'status'>>

// =============================================================================
// TaskAttemptRepository
// =============================================================================

export class TaskAttemptRepository implements Repository<TaskAttemptRecord, TaskAttemptInsert, TaskAttemptUpdate> {
  private db: DatabaseHandle

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

  /**
   * Get a task attempt by ID.
   */
  async get(id: UUID, tx?: TransactionHandle): Promise<TaskAttemptRecord | undefined> {
    const stmt = (tx?.db ?? this.db).prepare('SELECT * FROM task_attempts WHERE id = ?')
    const row = stmt.get(id) as TaskAttemptRecord | undefined
    return row
  }

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

    const stmt = (tx?.db ?? this.db).prepare(`
      INSERT INTO task_attempts (
        id, session_id, task_id, attempt_index,
        agent_id, status,
        failure_signature, failure_summary,
        started_at, completed_at,
        worker_result_json, metadata_json
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `)

    stmt.run(
      record.id,
      record.session_id,
      record.task_id,
      record.attempt_index,
      record.agent_id ?? null,
      status,
      record.failure_signature ?? null,
      record.failure_summary ?? null,
      record.started_at,
      record.completed_at ?? null,
      record.worker_result_json ?? null,
      record.metadata_json ?? null,
    )
  }

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

    // NOTE: status is NOT updatable here - only EventStore.project() writes status columns
    if (patch.agent_id !== undefined) {
      fields.push('agent_id = ?')
      values.push(patch.agent_id)
    }
    if (patch.failure_signature !== undefined) {
      fields.push('failure_signature = ?')
      values.push(patch.failure_signature)
    }
    if (patch.failure_summary !== undefined) {
      fields.push('failure_summary = ?')
      values.push(patch.failure_summary)
    }
    if (patch.completed_at !== undefined) {
      fields.push('completed_at = ?')
      values.push(patch.completed_at)
    }
    if (patch.worker_result_json !== undefined) {
      fields.push('worker_result_json = ?')
      values.push(patch.worker_result_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 task_attempts SET ${fields.join(', ')} WHERE id = ?`)
    stmt.run(...values)
  }

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

  /**
   * Get the next attempt index for a task (0-based).
   */
  async next_attempt_index(task_id: TaskID): Promise<number> {
    const stmt = this.db.prepare(
      'SELECT MAX(attempt_index) as max_index FROM task_attempts WHERE task_id = ?',
    )
    const row = stmt.get(task_id) as { max_index: number | null } | undefined
    return (row?.max_index ?? -1) + 1
  }

  /**
   * List all attempts for a task, ordered by attempt_index.
   */
  async list_by_task(task_id: TaskID): Promise<TaskAttemptRecord[]> {
    const stmt = this.db.prepare(
      'SELECT * FROM task_attempts WHERE task_id = ? ORDER BY attempt_index ASC',
    )
    return stmt.all(task_id) as TaskAttemptRecord[]
  }
}