import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { Database } from 'bun:sqlite';
import { hashToken } from '../src/auth';

const directory = mkdtempSync(join(tmpdir(), 'minabot-api-smoke-'));
const databasePath = join(directory, 'test.sqlite');
const probe = Bun.serve({ hostname: '127.0.0.1', port: 0, fetch: () => new Response() });
const port = probe.port!; probe.stop(true);
const origin = `http://127.0.0.1:${port}`;
const env = { ...process.env, NODE_ENV: 'production', PORT: String(port), APP_ORIGIN: origin, DATABASE_PATH: databasePath,
  DISCORD_CLIENT_ID: '', DISCORD_CLIENT_SECRET: '', AUTH_COOKIE_SECRET: 'smoke-test-only-secret-at-least-32-characters' };
const token = 's'.repeat(43);
let processHandle: ReturnType<typeof Bun.spawn> | undefined;
async function boot() {
  processHandle = Bun.spawn([process.execPath, 'scripts/start.ts'], { env, stdout: 'pipe', stderr: 'pipe' });
  for (let attempt = 0; attempt < 100; attempt++) {
    try { if ((await fetch(`${origin}/api/health`)).ok) return; } catch { /* Wait for startup. */ }
    if (processHandle.exitCode !== null) throw new Error(`Production server exited with ${processHandle.exitCode}`);
    await Bun.sleep(50);
  }
  throw new Error('Production server did not become healthy');
}
async function stop() { if (processHandle) { processHandle.kill(); await processHandle.exited; processHandle = undefined; } }
async function request(path: string, method = 'GET', body?: unknown, status = 200) {
  const response = await fetch(`${origin}/api${path}`, { method, headers: { Origin: origin, Cookie: `minabot_session=${token}`, 'Content-Type': 'application/json' }, body: body === undefined ? undefined : JSON.stringify(body) });
  const data = await response.json();
  if (response.status !== status) throw new Error(`${method} ${path}: ${response.status}: ${JSON.stringify(data)}`);
  return data as any;
}
function check(value: unknown, message: string): asserts value { if (!value) throw new Error(message); }
try {
  const migration = Bun.spawn([process.execPath, 'scripts/migrate.ts'], { env, stdout: 'pipe', stderr: 'pipe' });
  check(await migration.exited === 0, 'Fresh production migrations failed');
  const sqlite = new Database(databasePath); const now = Date.now();
  sqlite.query('INSERT INTO users (id, discord_id, username, timezone, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?)').run('smoke', 'smoke', 'Smoke', 'UTC', now, now);
  sqlite.query('INSERT INTO sessions (token_hash, user_id, created_at, expires_at) VALUES (?, ?, ?, ?)').run(hashToken(token), 'smoke', now, now + 3600000);
  sqlite.close();
  await boot();
  const date = (await request('/today')).date;
  const h = await request('/habits', 'POST', { name: 'HTTP hydration', method: 'count', target: 8 }, 201);
  const schedule = { type: 'weekdays', days: [new Date(`${date}T00:00:00Z`).getUTCDay()] };
  const t = await request('/habits', 'POST', { name: 'HTTP tasks', method: 'tasks', tasks: [{ name: 'Check', schedule }] }, 201);
  const renamedTask = await request(`/habits/${t.id}/tasks/${t.tasks[0].id}`, 'PATCH', { name: 'Renamed task' });
  check(JSON.stringify(renamedTask.schedule) === JSON.stringify(schedule), 'Task rename reset recurrence');
  await Promise.all([request(`/habits/${h.id}`, 'PATCH', { name: 'Concurrent hydration' }), request(`/habits/${h.id}`, 'PATCH', { unit: 'glasses' })]);
  const updated = await request(`/habits/${h.id}`);
  check(updated.name === 'Concurrent hydration' && updated.unit === 'glasses', 'Overlapping patches lost unrelated settings');
  await request(`/habits/${h.id}/days/${date}/progress`, 'PUT', { count: 7 });
  await request(`/habits/${t.id}/days/${date}/tasks/${t.tasks[0].id}`, 'PUT', { done: true });
  const c = await request('/charts', 'POST', { name: 'HTTP combined', habitIds: [h.id, t.id], settings: { mainColor: '#ff0000', shadeCount: 8 } }, 201);
  check((await request(`/charts/${c.id}/days/${date}`)).ratio === 0.5, 'Partial habit incorrectly contributed to combined score');
  const renamedChart = await request(`/charts/${c.id}`, 'PATCH', { name: 'Renamed chart' });
  check(renamedChart.settings.mainColor === '#ff0000' && renamedChart.settings.shadeCount === 8, 'Chart rename reset settings');
  await request(`/habits/${t.id}`, 'PATCH', { archived: true });
  await request(`/habits/${t.id}`, 'PATCH', { archived: false });
  check((await request(`/habits/${t.id}/days/${date}`)).complete, 'Archive and restore lost task completion');
  await stop(); await boot();
  check((await request(`/habits/${h.id}/days/${date}`)).value === 7, 'Count did not survive restart');
  check((await request(`/charts/${c.id}/days/${date}`)).ratio === 0.5, 'Tasks or membership did not survive restart');
  await request(`/habits/${h.id}/days/${date}/progress`, 'PUT', { count: 8 });
  const result = await request(`/charts/${c.id}/calendar?from=${date}&to=${date}`);
  check(result.days[0].ratio === 1, 'Correction did not update combined calendar');
  check((await fetch(`${origin}/api/habits`)).status === 401, 'HTTP authentication was bypassed');
  check((await fetch(`${origin}/api/habits`, { method: 'POST', headers: { Cookie: `minabot_session=${token}`, Origin: 'https://evil.example', 'Content-Type': 'application/json' }, body: '{}' })).status === 403, 'HTTP Origin enforcement was bypassed');
  console.log('Production HTTP smoke passed: migrations, count/task logs, combined scores, correction, PATCH preservation, overlapping writes, archive/restore, authentication, CSRF, and persistence across restart.');
} finally { await stop(); rmSync(directory, { recursive: true, force: true }); }
