/**
 * bttask — Task board SQLite store.
 * DB: ~/.local/share/agor/btmsg.db (shared with btmsg).
 * Uses bun:sqlite. Schema matches Rust bttask.rs.
 */

import { Database } from "bun:sqlite";
import { homedir } from "os";
import { mkdirSync } from "fs";
import { join } from "path";
import { randomUUID } from "crypto";

// ── DB path (same DB as btmsg) ──────────────────────────────────────────────

const DATA_DIR = join(homedir(), ".local", "share", "agor");
const DB_PATH = join(DATA_DIR, "btmsg.db");

// ── 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;

  constructor() {
    mkdirSync(DATA_DIR, { recursive: true });
    this.db = new Database(DB_PATH);
    this.db.exec("PRAGMA journal_mode = WAL");
    this.db.exec("PRAGMA busy_timeout = 5000");

    // Ensure tables exist (idempotent — btmsg-db may have created them)
    this.db.exec(`
      CREATE TABLE IF NOT EXISTS tasks (
        id TEXT PRIMARY KEY, title TEXT NOT NULL, description TEXT DEFAULT '',
        status TEXT DEFAULT 'todo', priority TEXT DEFAULT 'medium',
        assigned_to TEXT, created_by TEXT NOT NULL, group_id TEXT NOT NULL,
        parent_task_id TEXT, sort_order INTEGER DEFAULT 0,
        created_at TEXT DEFAULT (datetime('now')),
        updated_at TEXT DEFAULT (datetime('now')), version INTEGER DEFAULT 1
      );
      CREATE INDEX IF NOT EXISTS idx_tasks_group ON tasks(group_id);
      CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
      CREATE TABLE IF NOT EXISTS task_comments (
        id TEXT PRIMARY KEY, task_id TEXT NOT NULL, agent_id TEXT NOT NULL,
        content TEXT NOT NULL, created_at TEXT DEFAULT (datetime('now'))
      );
      CREATE INDEX IF NOT EXISTS idx_task_comments_task ON task_comments(task_id);
    `);

    // Migration: add version column if missing
    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;
  }

  deleteTask(taskId: string): void {
    this.db.query("DELETE FROM task_comments WHERE task_id = ?").run(taskId);
    this.db.query("DELETE FROM tasks WHERE id = ?").run(taskId);
  }

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

  addComment(taskId: string, agentId: string, content: string): string {
    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;
  }

  // ── Lifecycle ────────────────────────────────────────────────────────────

  close(): void {
    this.db.close();
  }
}

// Singleton
export const bttaskDb = new BttaskDb();
