/**
 * TaskRepository - CRUD + list_by_status, list_runnable_candidates for tasks table (§7)
 *
 * Implements Repository<TaskRecord, TaskInsert, TaskUpdate> per contracts §6.
 * Extended with TaskRepository interface per contracts §9.
 *
 * @module packages/runtime/src/storage/repositories/TaskRepository
 */

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

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

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

export type TaskStatus = 'pending' | 'running' | 'completed' | 'failed' | 'blocked' | 'cancelled' | 'interrupted'
export type TaskType = 'execute' | 'review' | 'debug' | 'compact' | 'mine_experience' | 'docs'

export interface TaskRecord {
  id: TaskID
  session_id: SessionID
  type: TaskType
  status: TaskStatus
  title: string
  task_spec_json: string
  worker_result_json?: string
  assigned_agent_id?: AgentID
  workspace_id?: WorkspaceID
  retry_count: number
  created_at: ISOTimeString
  started_at?: ISOTimeString
  completed_at?: ISOTimeString
  heartbeat_at?: ISOTimeString
  metadata_json?: string
}

export type TaskInsert = Omit<TaskRecord, 'id' | 'status'> & {
  id?: TaskID
  retry_count?: number
  worker_result_json?: string
  started_at?: ISOTimeString
  completed_at?: ISOTimeString
  heartbeat_at?: ISOTimeString
}

export type TaskUpdate = Partial<Omit<TaskRecord, 'id' | 'session_id' | 'created_at' | 'status'>>

// =============================================================================
// TaskRepository
// =============================================================================

export class TaskRepository implements Repository<TaskRecord, TaskInsert, TaskUpdate> {
  private db: DatabaseHandle

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

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

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

    const stmt = (tx?.db ?? this.db).prepare(`
      INSERT INTO tasks (
        id, session_id, type, status, title,
        task_spec_json, worker_result_json,
        assigned_agent_id, workspace_id,
        retry_count, created_at, started_at, completed_at, heartbeat_at,
        metadata_json
      ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    `)

    stmt.run(
      record.id,
      record.session_id,
      record.type,
      status,
      record.title,
      record.task_spec_json,
      record.worker_result_json ?? null,
      record.assigned_agent_id ?? null,
      record.workspace_id ?? null,
      record.retry_count ?? 0,
      record.created_at,
      record.started_at ?? null,
      record.completed_at ?? null,
      record.heartbeat_at ?? null,
      record.metadata_json ?? null,
    )
  }

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

    // NOTE: status is NOT updatable here - only EventStore.project() writes status columns
    if (patch.title !== undefined) {
      fields.push('title = ?')
      values.push(patch.title)
    }
    if (patch.task_spec_json !== undefined) {
      fields.push('task_spec_json = ?')
      values.push(patch.task_spec_json)
    }
    if (patch.worker_result_json !== undefined) {
      fields.push('worker_result_json = ?')
      values.push(patch.worker_result_json)
    }
    if (patch.assigned_agent_id !== undefined) {
      fields.push('assigned_agent_id = ?')
      values.push(patch.assigned_agent_id)
    }
    if (patch.workspace_id !== undefined) {
      fields.push('workspace_id = ?')
      values.push(patch.workspace_id)
    }
    if (patch.retry_count !== undefined) {
      fields.push('retry_count = ?')
      values.push(patch.retry_count)
    }
    if (patch.started_at !== undefined) {
      fields.push('started_at = ?')
      values.push(patch.started_at)
    }
    if (patch.completed_at !== undefined) {
      fields.push('completed_at = ?')
      values.push(patch.completed_at)
    }
    if (patch.heartbeat_at !== undefined) {
      fields.push('heartbeat_at = ?')
      values.push(patch.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 tasks SET ${fields.join(', ')} WHERE id = ?`)
    stmt.run(...values)
  }

  // =============================================================================
  // Extra methods (TaskRepository interface per contracts §9)
  // =============================================================================

  /**
   * List tasks by status filter.
   */
  async list_by_status(session_id: SessionID, statuses: TaskStatus[]): Promise<TaskRecord[]> {
    if (statuses.length === 0) {
      const stmt = this.db.prepare(
        'SELECT * FROM tasks WHERE session_id = ? ORDER BY created_at ASC',
      )
      return stmt.all(session_id) as TaskRecord[]
    }

    const placeholders = statuses.map(() => '?').join(', ')
    const stmt = this.db.prepare(
      `SELECT * FROM tasks WHERE session_id = ? AND status IN (${placeholders}) ORDER BY created_at ASC`,
    )
    return stmt.all(session_id, ...statuses) as TaskRecord[]
  }

  /**
   * List runnable task candidates - tasks that have all dependencies satisfied.
   * A task is runnable if:
   *   - status is 'pending'
   *   - all 'hard' dependencies are in 'completed' status
   */
  async list_runnable_candidates(session_id: SessionID): Promise<TaskRecord[]> {
    // First get all pending tasks
    const pendingStmt = this.db.prepare(`
      SELECT * FROM tasks
      WHERE session_id = ? AND status = 'pending'
      ORDER BY created_at ASC
    `)
    const pendingTasks = pendingStmt.all(session_id) as TaskRecord[]

    // Filter to only those with all hard dependencies satisfied
    const runnable: TaskRecord[] = []

    for (const task of pendingTasks) {
      const depsStmt = this.db.prepare(`
        SELECT td.depends_on_task_id, t.status as dep_status
        FROM task_dependencies td
        JOIN tasks t ON td.depends_on_task_id = t.id
        WHERE td.task_id = ? AND td.dependency_type = 'hard'
      `)
      const deps = depsStmt.all(task.id) as Array<{ depends_on_task_id: string; dep_status: string }>

      const allHardDepsCompleted = deps.every((d) => d.dep_status === 'completed')
      if (allHardDepsCompleted) {
        runnable.push(task)
      }
    }

    return runnable
  }
}