import { test, expect } from '@playwright/test';
import Database from 'better-sqlite3';
import { readFileSync } from 'node:fs';
import { resolve, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';

// The tool-approval pause can only be produced naturally by a live LLM (the
// agent must call RequestTool). In this LLM-less E2E we seed the engine state
// directly into the throwaway DB: a job parked with wait_reason='tool_request'
// + a pending tool_request, then drive the inline approval card in the chat.
// The DB path is handed off by playwright.config.ts via ui/.e2e-db-path.
const __dirname = dirname(fileURLToPath(import.meta.url));
const dbPath = readFileSync(resolve(__dirname, '..', '.e2e-db-path'), 'utf-8').trim();

test('inline tool-approval: the card renders and Approve grants + resolves the request', async ({ page, request }) => {
  // 1. Create a task (spawns a job).
  const created = await request.post('/api/local/tasks', { data: { body: 'e2e tool request', piece: 'chat' } });
  expect(created.ok()).toBeTruthy();
  const taskId = (await created.json()).task.id as number;

  // 2. Seed: park the spawned job for tool approval + a pending request.
  const db = new Database(dbPath);
  db.pragma('busy_timeout = 8000');
  let jobId: string | undefined;
  for (let i = 0; i < 60 && !jobId; i++) {
    const row = db.prepare(`SELECT id FROM jobs WHERE repo = ? ORDER BY created_at DESC LIMIT 1`)
      .get(`local/task-${taskId}`) as { id: string } | undefined;
    if (row) jobId = row.id;
    else await new Promise((r) => setTimeout(r, 100));
  }
  if (!jobId) throw new Error('spawned job row never appeared');
  db.prepare(`UPDATE jobs SET status='waiting_human', wait_reason='tool_request' WHERE id = ?`).run(jobId);
  const reqId = 'e2e-tool-req-1';
  db.prepare(
    `INSERT INTO tool_requests (id, task_id, job_id, piece_name, movement_name, tool_name, reason, category, status)
     VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending')`,
  ).run(reqId, String(taskId), jobId, 'chat', 'respond', 'WebSearch', 'need to search the web', 'requested');
  db.close();

  // 3. Open the task; the inline approval card must render with tool + reason.
  await page.goto(`/ui?task=${taskId}`);
  const card = page.locator('[data-testid="tool-request-WebSearch"]:visible');
  await expect(card).toBeVisible();
  await expect(card).toContainText('WebSearch');
  await expect(card).toContainText('need to search the web');

  // While awaiting approval the normal composer is locked — sending a regular
  // message here would spawn a duplicate parallel job on resume.
  await expect(page.locator('textarea:visible').first()).toBeDisabled();

  // 4. Approve → the card disappears (no longer pending).
  await page.locator('[data-testid="tool-request-approve-WebSearch"]:visible').click();
  await expect(page.getByTestId('tool-request-WebSearch')).toHaveCount(0);

  // 5. Backend: request approved, tool granted to the task, job re-queued.
  const verify = new Database(dbPath, { readonly: true });
  const tr = verify.prepare(`SELECT status FROM tool_requests WHERE id = ?`).get(reqId) as { status: string };
  const task = verify.prepare(`SELECT granted_tools FROM local_tasks WHERE id = ?`).get(taskId) as { granted_tools: string | null };
  verify.close();
  expect(tr.status).toBe('approved');
  expect(task.granted_tools ?? '').toContain('WebSearch');
});

test('inline tool-approval: Deny resolves without granting', async ({ page, request }) => {
  const created = await request.post('/api/local/tasks', { data: { body: 'e2e deny', piece: 'chat' } });
  const taskId = (await created.json()).task.id as number;

  const db = new Database(dbPath);
  db.pragma('busy_timeout = 8000');
  let jobId: string | undefined;
  for (let i = 0; i < 60 && !jobId; i++) {
    const row = db.prepare(`SELECT id FROM jobs WHERE repo = ? ORDER BY created_at DESC LIMIT 1`)
      .get(`local/task-${taskId}`) as { id: string } | undefined;
    if (row) jobId = row.id;
    else await new Promise((r) => setTimeout(r, 100));
  }
  if (!jobId) throw new Error('spawned job row never appeared');
  db.prepare(`UPDATE jobs SET status='waiting_human', wait_reason='tool_request' WHERE id = ?`).run(jobId);
  const reqId = 'e2e-tool-req-2';
  db.prepare(
    `INSERT INTO tool_requests (id, task_id, job_id, piece_name, movement_name, tool_name, reason, category, status)
     VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending')`,
  ).run(reqId, String(taskId), jobId, 'chat', 'respond', 'Bash', 'run a command', 'requested');
  db.close();

  await page.goto(`/ui?task=${taskId}`);
  await expect(page.locator('[data-testid="tool-request-Bash"]:visible')).toBeVisible();
  await page.locator('[data-testid="tool-request-deny-Bash"]:visible').click();
  await expect(page.getByTestId('tool-request-Bash')).toHaveCount(0);

  const verify = new Database(dbPath, { readonly: true });
  const tr = verify.prepare(`SELECT status FROM tool_requests WHERE id = ?`).get(reqId) as { status: string };
  const task = verify.prepare(`SELECT granted_tools FROM local_tasks WHERE id = ?`).get(taskId) as { granted_tools: string | null };
  verify.close();
  expect(tr.status).toBe('denied');
  expect(task.granted_tools ?? '').not.toContain('Bash');
});
