import { Router, Request, Response } from 'express';
import { SessionManager, type BrowserSession, CAPTCHA_POOL_SESSION_ID } from '../engine/browser-session.js';
import type { Repository } from '../db/repository.js';
import { logger } from '../logger.js';
import { buildNovncPath, isNovncStaticInstalled } from './novnc-proxy.js';
import { canUserSeeTask, canEditEntity } from './visibility.js';

/**
 * 2026-05 redesign: CAPTCHA Pool (admin 専用) と Task Session (タスク
 * visibility ベース) を分離した API。
 *
 * - GET /captcha-pool                : admin only. Pool の noVNC パス + captchaPending
 * - DELETE /captcha-pool             : admin only. Pool を destroy (次の CAPTCHA で再生成)
 * - GET /task-session/:taskId        : visibility 通過なら novncPath、なければ available:false
 * - POST /task-session/:taskId/release: owner or admin. その taskId の session を destroy
 * - GET /                            : 自分が見えるタスクの task session 一覧 (Pool は除外)
 * - GET /:id                         : 直 sessionId 指定 (admin / 旧来 owner)。kind=='task' のときは taskId 経由を推奨
 * - DELETE /:id                      : admin or task owner
 *
 * 旧 /search-session, POST / は廃止 (Plan の clean break)。
 */

function isAdmin(req: Request): boolean {
  const user = req.user as Express.User | undefined;
  return user?.role === 'admin';
}

function getUser(req: Request): Express.User | undefined {
  return req.user as Express.User | undefined;
}

/** auth 未設定 (dev モード) では req.user が undefined。その場合は全許可で互換維持 */
function isUnauthenticatedDev(req: Request): boolean {
  return getUser(req) === undefined;
}

function serializeTaskSession(session: BrowserSession) {
  return {
    id: session.id,
    kind: session.kind,
    taskId: session.taskId,
    state: session.state,
    novncPath: buildNovncPath(session.id),
    lockedByJobId: session.lockedByJobId,
    createdAt: session.createdAt.toISOString(),
    lastActiveAt: session.lastActiveAt.toISOString(),
  };
}

/**
 * 指定 task session に user がアクセスできるかを判定する (visibility ベース)。
 * Pool は admin only。dev モードは全許可。
 */
async function canViewSession(
  req: Request,
  session: BrowserSession,
  repo: Repository,
): Promise<boolean> {
  if (isUnauthenticatedDev(req)) return true;
  const user = getUser(req)!;
  if (user.role === 'admin') return true;
  if (session.kind === 'pool') return false;
  if (session.kind === 'task' && session.taskId) {
    const taskIdNum = Number(session.taskId);
    if (!Number.isFinite(taskIdNum)) return false;
    const task = await repo.getLocalTask(taskIdNum);
    return task ? canUserSeeTask(user, task) : false;
  }
  // 旧来 (kind 未設定): owner だけ
  return session.userId === user.id;
}

/**
 * 指定 task session を user が destroy / release できるかを判定する。
 * - admin: 常に可
 * - task owner: 可
 * - dev モード: 可
 */
async function canControlSession(
  req: Request,
  session: BrowserSession,
  repo: Repository,
): Promise<boolean> {
  if (isUnauthenticatedDev(req)) return true;
  const user = getUser(req)!;
  if (user.role === 'admin') return true;
  if (session.kind === 'pool') return false;
  if (session.kind === 'task' && session.taskId) {
    const taskIdNum = Number(session.taskId);
    if (!Number.isFinite(taskIdNum)) return false;
    const task = await repo.getLocalTask(taskIdNum);
    if (!task) return false;
    return canEditEntity(user, task);
  }
  return session.userId === user.id;
}

export function createBrowserApi(sessionManager: SessionManager | null, repo: Repository): Router {
  const router = Router();

  // sessionManager が null (Xvfb 等が無い環境) でも /captcha-pool / /task-session
  // は available: false を返したい。503 にしてしまうと UI 側でエラー扱いされてしまう。
  if (!sessionManager) {
    router.get('/captcha-pool', (_req: Request, res: Response) => {
      res.json({ available: false });
    });
    router.get('/task-session/:taskId', (_req: Request, res: Response) => {
      res.json({ available: false });
    });
    router.all('*', (_req: Request, res: Response) => {
      res.status(503).json({ error: 'Browser sessions not available (missing system dependencies)' });
    });
    return router;
  }

  // --- CAPTCHA Pool (admin only) ---

  router.get('/captcha-pool', (req: Request, res: Response) => {
    if (!isUnauthenticatedDev(req) && !isAdmin(req)) {
      res.status(403).json({ error: 'Admin role required' });
      return;
    }
    const pool = sessionManager.getSession(CAPTCHA_POOL_SESSION_ID);
    if (!pool) {
      res.json({ available: false });
      return;
    }
    if (!isNovncStaticInstalled()) {
      res.json({ available: false, reason: 'novnc_not_installed' });
      return;
    }
    res.json({
      available: true,
      sessionId: pool.id,
      novncPath: buildNovncPath(pool.id),
      display: pool.display,
      captchaPending: pool.captchaPending === true,
      createdAt: pool.createdAt.toISOString(),
    });
  });

  router.delete('/captcha-pool', async (req: Request, res: Response) => {
    if (!isUnauthenticatedDev(req) && !isAdmin(req)) {
      res.status(403).json({ error: 'Admin role required' });
      return;
    }
    // Pool destroy 時は web.ts の persistentContexts も連動して破棄する
    // (Cookie の生残りで認証状態が混乱するのを防ぐ)
    try {
      const webMod = await import('../engine/tools/web.js') as { clearPersistentContexts?: () => void };
      webMod.clearPersistentContexts?.();
    } catch { /* ignore */ }
    await sessionManager.destroySession(CAPTCHA_POOL_SESSION_ID);
    res.json({ ok: true });
  });

  // --- Task Session (visibility-aware) ---

  router.get('/task-session/:taskId', async (req: Request, res: Response) => {
    const taskId = req.params.taskId;
    const session = sessionManager
      .listSessions()
      .find((s) => s.kind === 'task' && s.taskId === taskId);
    if (!session) {
      res.json({ available: false });
      return;
    }
    if (!(await canViewSession(req, session, repo))) {
      // 認可失敗は available: false にして session 存在情報を漏らさない
      res.json({ available: false });
      return;
    }
    if (!isNovncStaticInstalled()) {
      // session は存在するが、iframe で読む vnc.html が配置されていない。
      // UI 側で「scripts/setup-novnc.sh を実行してください」と案内する。
      res.json({ available: false, reason: 'novnc_not_installed' });
      return;
    }
    res.json({
      available: true,
      sessionId: session.id,
      novncPath: buildNovncPath(session.id),
      display: session.display,
      state: session.state,
      lockedByJobId: session.lockedByJobId,
      createdAt: session.createdAt.toISOString(),
      lastActiveAt: session.lastActiveAt.toISOString(),
    });
  });

  router.post('/task-session/:taskId/release', async (req: Request, res: Response) => {
    const taskId = req.params.taskId;
    const session = sessionManager
      .listSessions()
      .find((s) => s.kind === 'task' && s.taskId === taskId);
    if (!session) {
      res.status(404).json({ error: 'Task session not found' });
      return;
    }
    if (!(await canControlSession(req, session, repo))) {
      res.status(403).json({ error: 'Forbidden' });
      return;
    }
    await sessionManager.destroySession(session.id);
    res.json({ ok: true });
  });

  // --- Generic list / detail (task sessions only; pool excluded) ---

  router.get('/', async (req: Request, res: Response) => {
    const taskSessions = sessionManager.listSessions().filter((s) => s.kind === 'task');
    const visible: BrowserSession[] = [];
    for (const s of taskSessions) {
      if (await canViewSession(req, s, repo)) visible.push(s);
    }
    res.json({ sessions: visible.map(serializeTaskSession) });
  });

  router.get('/:id', async (req: Request, res: Response) => {
    const session = sessionManager.getSession(req.params.id);
    if (!session || session.kind === 'pool') {
      // Pool は /captcha-pool 経由でのみアクセスさせる (id 直指定では不可)
      res.status(404).json({ error: 'Session not found' });
      return;
    }
    if (!(await canViewSession(req, session, repo))) {
      res.status(404).json({ error: 'Session not found' });
      return;
    }
    res.json(serializeTaskSession(session));
  });

  router.delete('/:id', async (req: Request, res: Response) => {
    const session = sessionManager.getSession(req.params.id);
    if (!session || session.kind === 'pool') {
      res.status(404).json({ error: 'Session not found' });
      return;
    }
    if (!(await canControlSession(req, session, repo))) {
      res.status(404).json({ error: 'Session not found' });
      return;
    }
    await sessionManager.destroySession(session.id);
    res.json({ ok: true });
  });

  router.post('/:id/release', async (req: Request, res: Response) => {
    const session = sessionManager.getSession(req.params.id);
    if (!session || session.kind === 'pool') {
      res.status(404).json({ error: 'Session not found' });
      return;
    }
    if (!(await canControlSession(req, session, repo))) {
      res.status(404).json({ error: 'Session not found' });
      return;
    }
    sessionManager.releaseToAgent(session.id);
    if (session.lockedByJobId) {
      try {
        await repo.updateJob(session.lockedByJobId, {
          status: 'queued',
          waitReason: null,
        });
      } catch (err) {
        logger.warn(`[browser-api] failed to re-queue job ${session.lockedByJobId}: ${(err as Error).message}`);
      }
    }
    res.json({ ok: true, state: 'agent_controlled' });
  });

  return router;
}
