/**
 * Unit tests for BttaskDb — in-memory SQLite.
 */
import { describe, it, expect, beforeEach } from "bun:test";
import { Database } from "bun:sqlite";
import { randomUUID } from "crypto";

const TASK_SCHEMA = `
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);
`;

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

function createDb(): Database {
  const db = new Database(":memory:");
  db.exec(TASK_SCHEMA);
  return db;
}

function createTask(db: Database, title: string, groupId: string, createdBy: string): string {
  const id = randomUUID();
  db.query(
    `INSERT INTO tasks (id, title, description, priority, group_id, created_by)
     VALUES (?1, ?2, '', 'medium', ?3, ?4)`
  ).run(id, title, groupId, createdBy);
  return id;
}

function updateTaskStatus(db: Database, taskId: string, status: string, expectedVersion: number): number {
  if (!VALID_STATUSES.includes(status)) {
    throw new Error(`Invalid status '${status}'`);
  }
  const result = 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;
}

describe("BttaskDb", () => {
  let db: Database;

  beforeEach(() => {
    db = createDb();
  });

  // ── createTask ────────────────────────────────────────────────────────

  describe("createTask", () => {
    it("inserts a task with default status=todo and version=1", () => {
      const id = createTask(db, "Fix bug", "grp-1", "agent-1");
      const row = db.query<{ title: string; status: string; version: number }, [string]>(
        "SELECT title, status, COALESCE(version, 1) as version FROM tasks WHERE id = ?"
      ).get(id);
      expect(row!.title).toBe("Fix bug");
      expect(row!.status).toBe("todo");
      expect(row!.version).toBe(1);
    });
  });

  // ── listTasks ─────────────────────────────────────────────────────────

  describe("listTasks", () => {
    it("returns tasks filtered by groupId", () => {
      createTask(db, "T1", "grp-a", "agent");
      createTask(db, "T2", "grp-a", "agent");
      createTask(db, "T3", "grp-b", "agent");

      const rows = db.query<{ title: string }, [string]>(
        "SELECT title FROM tasks WHERE group_id = ?"
      ).all("grp-a");
      expect(rows).toHaveLength(2);
    });

    it("returns empty array for unknown group", () => {
      const rows = db.query<{ id: string }, [string]>(
        "SELECT id FROM tasks WHERE group_id = ?"
      ).all("nonexistent");
      expect(rows).toHaveLength(0);
    });
  });

  // ── updateTaskStatus ──────────────────────────────────────────────────

  describe("updateTaskStatus", () => {
    it("succeeds with correct version", () => {
      const id = createTask(db, "Task", "g", "a");
      const newVersion = updateTaskStatus(db, id, "progress", 1);
      expect(newVersion).toBe(2);

      const row = db.query<{ status: string; version: number }, [string]>(
        "SELECT status, version FROM tasks WHERE id = ?"
      ).get(id);
      expect(row!.status).toBe("progress");
      expect(row!.version).toBe(2);
    });

    it("throws on version conflict", () => {
      const id = createTask(db, "Task", "g", "a");
      updateTaskStatus(db, id, "progress", 1); // version -> 2
      expect(() => updateTaskStatus(db, id, "done", 1)).toThrow("version conflict");
    });

    it("rejects invalid status", () => {
      const id = createTask(db, "Task", "g", "a");
      expect(() => updateTaskStatus(db, id, "invalid", 1)).toThrow("Invalid status");
    });

    it("accepts all valid statuses", () => {
      for (const status of VALID_STATUSES) {
        const id = createTask(db, `Task-${status}`, "g", "a");
        const v = updateTaskStatus(db, id, status, 1);
        expect(v).toBe(2);
      }
    });
  });

  // ── deleteTask ────────────────────────────────────────────────────────

  describe("deleteTask", () => {
    it("deletes task and cascades comments", () => {
      const taskId = createTask(db, "Doomed", "g", "a");
      const commentId = randomUUID();
      db.query(
        "INSERT INTO task_comments (id, task_id, agent_id, content) VALUES (?, ?, ?, ?)"
      ).run(commentId, taskId, "a", "a comment");

      // Delete in transaction (mirrors BttaskDb.deleteTask)
      const tx = db.transaction(() => {
        db.query("DELETE FROM task_comments WHERE task_id = ?").run(taskId);
        db.query("DELETE FROM tasks WHERE id = ?").run(taskId);
      });
      tx();

      expect(db.query<{ id: string }, [string]>("SELECT id FROM tasks WHERE id = ?").get(taskId)).toBeNull();
      expect(
        db.query<{ id: string }, [string]>("SELECT id FROM task_comments WHERE task_id = ?").get(taskId)
      ).toBeNull();
    });
  });

  // ── addComment ────────────────────────────────────────────────────────

  describe("addComment", () => {
    it("adds comment to existing task", () => {
      const taskId = createTask(db, "Task", "g", "a");
      const task = db.query<{ id: string }, [string]>("SELECT id FROM tasks WHERE id = ?").get(taskId);
      expect(task).not.toBeNull();

      const cid = randomUUID();
      db.query(
        "INSERT INTO task_comments (id, task_id, agent_id, content) VALUES (?, ?, ?, ?)"
      ).run(cid, taskId, "agent-1", "looks good");

      const comments = db.query<{ content: string }, [string]>(
        "SELECT content FROM task_comments WHERE task_id = ? ORDER BY created_at ASC"
      ).all(taskId);
      expect(comments).toHaveLength(1);
      expect(comments[0].content).toBe("looks good");
    });

    it("validates task exists before adding comment", () => {
      const task = db.query<{ id: string }, [string]>(
        "SELECT id FROM tasks WHERE id = ?"
      ).get("nonexistent");
      expect(task).toBeNull(); // Would throw in BttaskDb.addComment
    });
  });

  // ── reviewQueueCount ──────────────────────────────────────────────────

  describe("reviewQueueCount", () => {
    it("counts tasks with status=review in group", () => {
      createTask(db, "T1", "g", "a");
      const t2 = createTask(db, "T2", "g", "a");
      const t3 = createTask(db, "T3", "g", "a");
      updateTaskStatus(db, t2, "review", 1);
      updateTaskStatus(db, t3, "review", 1);

      const row = db.query<{ cnt: number }, [string]>(
        "SELECT COUNT(*) AS cnt FROM tasks WHERE group_id = ? AND status = 'review'"
      ).get("g");
      expect(row!.cnt).toBe(2);
    });

    it("returns 0 when no review tasks", () => {
      createTask(db, "T1", "g", "a");
      const row = db.query<{ cnt: number }, [string]>(
        "SELECT COUNT(*) AS cnt FROM tasks WHERE group_id = ? AND status = 'review'"
      ).get("g");
      expect(row!.cnt).toBe(0);
    });
  });
});
