/**
 * bttask — Task board SQLite store.
 * DB: ~/.local/share/agor/btmsg.db (shared with btmsg).
 * Uses bun:sqlite. Schema matches Rust bttask.rs.
 *
 * Accepts a Database handle from BtmsgDb to avoid double-opening the same file.
 */

import { Database } from "bun:sqlite";
import { randomUUID } from "crypto";

// ── Types ────────────────────────────────────────────────────────────────────

export interface Task {
  id: string;
  title: string;
  description: string;
  status: string;
  priority: string;
  assignedTo: string | null;
  createdBy: string;
  groupId: string;
  parentTaskId: string | null;
  sortOrder: number;
  createdAt: string;
  updatedAt: string;
  version: number;
}

export interface TaskComment {
  id: string;
  taskId: string;
  agentId: string;
  content: string;
  createdAt: string;
}

const VALID_STATUSES = ["todo", "progress", "review", "done", "blocked"] as const;

// ── BttaskDb class ───────────────────────────────────────────────────────────

export class BttaskDb {
  private db: Database;

  /**
   * @param db — shared Database handle (from BtmsgDb.getHandle()).
   *   Tables are created by BtmsgDb's TASK_SCHEMA; this class is query-only.
   */
  constructor(db: Database) {
    this.db = db;

    // Migration: add version column if missing (idempotent)
    try {
      this.db.exec("ALTER TABLE tasks ADD COLUMN version INTEGER DEFAULT 1");
    } catch { /* column already exists */ }
  }

  // ── Tasks ────────────────────────────────────────────────────────────────

  listTasks(groupId: string): Task[] {
    return this.db.query<{
      id: string; title: string; description: string; status: string;
      priority: string; assigned_to: string | null; created_by: string;
      group_id: string; parent_task_id: string | null; sort_order: number;
      created_at: string; updated_at: string; version: number;
    }, [string]>(
      `SELECT id, title, description, status, priority, assigned_to,
              created_by, group_id, parent_task_id, sort_order,
              created_at, updated_at, COALESCE(version, 1) AS version
       FROM tasks WHERE group_id = ?
       ORDER BY sort_order ASC, created_at DESC`
    ).all(groupId).map(r => ({
      id: r.id, title: r.title, description: r.description ?? '',
      status: r.status ?? 'todo', priority: r.priority ?? 'medium',
      assignedTo: r.assigned_to, createdBy: r.created_by,
      groupId: r.group_id, parentTaskId: r.parent_task_id,
      sortOrder: r.sort_order ?? 0,
      createdAt: r.created_at ?? '', updatedAt: r.updated_at ?? '',
      version: r.version ?? 1,
    }));
  }

  createTask(
    title: string, description: string, priority: string,
    groupId: string, createdBy: string, assignedTo?: string,
  ): string {
    const id = randomUUID();
    this.db.query(
      `INSERT INTO tasks (id, title, description, priority, group_id, created_by, assigned_to)
       VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)`
    ).run(id, title, description, priority, groupId, createdBy, assignedTo ?? null);
    return id;
  }

  /**
   * Update task status with optimistic locking.
   * Returns new version on success. Throws on version conflict.
   */
  updateTaskStatus(taskId: string, status: string, expectedVersion: number): number {
    if (!VALID_STATUSES.includes(status as typeof VALID_STATUSES[number])) {
      throw new Error(`Invalid status '${status}'. Valid: ${VALID_STATUSES.join(', ')}`);
    }

    const result = this.db.query(
      `UPDATE tasks SET status = ?1, version = version + 1, updated_at = datetime('now')
       WHERE id = ?2 AND version = ?3`
    ).run(status, taskId, expectedVersion);

    if ((result as { changes: number }).changes === 0) {
      throw new Error("Task was modified by another agent (version conflict)");
    }

    return expectedVersion + 1;
  }

  /** Fix #9 (Codex audit): Wrap delete in transaction for atomicity. */
  deleteTask(taskId: string): void {
    const tx = this.db.transaction(() => {
      this.db.query("DELETE FROM task_comments WHERE task_id = ?").run(taskId);
      this.db.query("DELETE FROM tasks WHERE id = ?").run(taskId);
    });
    tx();
  }

  // ── Comments ─────────────────────────────────────────────────────────────

  /** Fix #9 (Codex audit): Validate task exists before adding comment. */
  addComment(taskId: string, agentId: string, content: string): string {
    const task = this.db.query<{ id: string }, [string]>(
      "SELECT id FROM tasks WHERE id = ?"
    ).get(taskId);
    if (!task) {
      throw new Error(`Task '${taskId}' not found`);
    }

    const id = randomUUID();
    this.db.query(
      `INSERT INTO task_comments (id, task_id, agent_id, content)
       VALUES (?1, ?2, ?3, ?4)`
    ).run(id, taskId, agentId, content);
    return id;
  }

  listComments(taskId: string): TaskComment[] {
    return this.db.query<{
      id: string; task_id: string; agent_id: string;
      content: string; created_at: string;
    }, [string]>(
      `SELECT id, task_id, agent_id, content, created_at
       FROM task_comments WHERE task_id = ?
       ORDER BY created_at ASC`
    ).all(taskId).map(r => ({
      id: r.id, taskId: r.task_id, agentId: r.agent_id,
      content: r.content, createdAt: r.created_at ?? '',
    }));
  }

  // ── Review queue ─────────────────────────────────────────────────────────

  reviewQueueCount(groupId: string): number {
    const row = this.db.query<{ cnt: number }, [string]>(
      "SELECT COUNT(*) AS cnt FROM tasks WHERE group_id = ? AND status = 'review'"
    ).get(groupId);
    return row?.cnt ?? 0;
  }

  // No close() — BtmsgDb owns the shared database handle.
}

/** Create a BttaskDb using the shared btmsg database handle. */
export function createBttaskDb(db: Database): BttaskDb {
  return new BttaskDb(db);
}
