import express, { Request, Response } from 'express';
import { readdirSync, statSync, readFileSync, mkdirSync, existsSync } from 'fs';
import { join, extname, resolve, sep } from 'path';
import { Repository, localTaskRepoName } from '../db/repository.js';
import { logger } from '../logger.js';
import { parseTaskId } from './validation.js';
import { checkTaskOwnership, ensurePathWithin, isPathEscapeError, isJobWithinWorkspace, setUntrustedFileResponseHeaders } from './local-api-helpers.js';

function sanitizeTaskForPublic(task: Record<string, unknown>): Record<string, unknown> {
  const { ownerId, workspacePath, body, ...safe } = task;
  return safe;
}

export function mountShareApi(app: express.Application, repo: Repository): void {
  // ── 公開エンドポイント（認証不要） ──

  app.get('/api/shared/:token', async (req: Request, res: Response) => {
    try {
      const task = await repo.getLocalTaskByShareToken(req.params.token);
      if (!task) { res.status(404).json({ error: 'Not found' }); return; }
      res.json({ task: sanitizeTaskForPublic(task as unknown as Record<string, unknown>) });
    } catch (err) {
      logger.error(`Shared task API error: ${err}`);
      res.status(500).json({ error: 'Failed to fetch shared task' });
    }
  });

  app.get('/api/shared/:token/comments', async (req: Request, res: Response) => {
    try {
      const task = await repo.getLocalTaskByShareToken(req.params.token);
      if (!task) { res.status(404).json({ error: 'Not found' }); return; }
      const comments = await repo.listLocalTaskComments(task.id);
      res.json({ comments });
    } catch (err) {
      logger.error(`Shared comments API error: ${err}`);
      res.status(500).json({ error: 'Failed to fetch comments' });
    }
  });

  app.get('/api/shared/:token/files', async (req: Request, res: Response) => {
    try {
      const task = await repo.getLocalTaskByShareToken(req.params.token);
      if (!task || !task.workspacePath) { res.status(404).json({ error: 'Not found' }); return; }

      const relativeDir = String(req.query.path ?? '').replace(/^\/+/, '').replace(/\/+$/, '');
      const rootDir = join(task.workspacePath, 'output');
      mkdirSync(rootDir, { recursive: true });
      const dirPath = ensurePathWithin(rootDir, relativeDir);
      const entries = readdirSync(dirPath, { withFileTypes: true }).map((entry) => {
        const stat = statSync(join(dirPath, entry.name));
        return {
          name: entry.name,
          path: relativeDir ? `${relativeDir}/${entry.name}` : entry.name,
          kind: entry.isDirectory() ? 'directory' : 'file',
          size: stat.size,
          modifiedAt: stat.mtime.toISOString(),
        };
      });
      res.json({ basePath: 'output', path: relativeDir, entries });
    } catch (err) {
      if (isPathEscapeError(err)) {
        res.status(400).json({ error: 'Path escapes workspace' });
        return;
      }
      logger.error(`Shared files API error: ${err}`);
      res.status(500).json({ error: 'Failed to list files' });
    }
  });

  app.get('/api/shared/:token/files/content', async (req: Request, res: Response) => {
    try {
      const task = await repo.getLocalTaskByShareToken(req.params.token);
      if (!task || !task.workspacePath) { res.status(404).json({ error: 'Not found' }); return; }

      const relativePath = String(req.query.path ?? '').replace(/^\/+/, '');
      if (!relativePath) { res.status(400).json({ error: 'path is required' }); return; }

      const rootDir = join(task.workspacePath, 'output');
      const filePath = ensurePathWithin(rootDir, relativePath);
      const stat = statSync(filePath);
      if (!stat.isFile()) { res.status(400).json({ error: 'path must point to a file' }); return; }

      setUntrustedFileResponseHeaders(res);
      res.setHeader('Content-Type', 'text/plain; charset=utf-8');
      res.send(readFileSync(filePath, 'utf-8'));
    } catch (err) {
      if (isPathEscapeError(err)) {
        res.status(400).json({ error: 'Path escapes workspace' });
        return;
      }
      logger.error(`Shared file content API error: ${err}`);
      res.status(500).json({ error: 'Failed to read file' });
    }
  });

  app.get('/api/shared/:token/files/raw', async (req: Request, res: Response) => {
    try {
      const task = await repo.getLocalTaskByShareToken(req.params.token);
      if (!task || !task.workspacePath) { res.status(404).json({ error: 'Not found' }); return; }

      const relativePath = String(req.query.path ?? '').replace(/^\/+/, '');
      if (!relativePath) { res.status(400).json({ error: 'path is required' }); return; }

      const rootDir = join(task.workspacePath, 'output');
      const filePath = ensurePathWithin(rootDir, relativePath);
      const stat = statSync(filePath);
      if (!stat.isFile()) { res.status(400).json({ error: 'path must point to a file' }); return; }

      setUntrustedFileResponseHeaders(res);
      res.type(extname(filePath) || 'application/octet-stream');
      res.send(readFileSync(filePath));
    } catch (err) {
      if (isPathEscapeError(err)) {
        res.status(400).json({ error: 'Path escapes workspace' });
        return;
      }
      logger.error(`Shared file raw API error: ${err}`);
      res.status(500).json({ error: 'Failed to read file' });
    }
  });

  app.get('/api/shared/:token/subtasks/activities', async (req: Request, res: Response) => {
    try {
      const task = await repo.getLocalTaskByShareToken(req.params.token);
      if (!task) { res.status(404).json({ error: 'Not found' }); return; }

      const latestJob = await repo.getLatestJobForIssue(localTaskRepoName(task.id), task.id);
      if (!latestJob) { res.json({ subtasks: [] }); return; }

      const subJobs = await repo.getSubJobs(latestJob.id);
      const subtasks = subJobs.map(job => ({
        jobId: job.id,
        issueNumber: job.issueNumber,
        status: job.status,
        currentMovement: job.currentMovement ?? null,
        currentActivity: job.currentActivity ?? null,
        activityLog: '',
      }));
      res.json({ subtasks });
    } catch (err) {
      logger.error(`Shared subtask activities API error: ${err}`);
      res.status(500).json({ error: 'Failed to fetch subtask activities' });
    }
  });

  // 共有トークン → サブジョブの解決（封じ込め付き）。
  // 1. token が有効なタスクを引く（失効/不正は null）
  // 2. jobId のサブジョブを引く（worktreePath 必須）
  // 3. サブジョブの worktree が共有タスクの workspacePath 配下であることを要求
  //    （= 他タスクのサブタスクへ token を流用しても 404）。
  // 認証付き版（subtask-files-api / subtask-activity-api）と同じ封じ込め流儀。
  async function resolveSharedSubJob(
    token: string,
  ): Promise<{ workspacePath: string } | null> {
    const task = await repo.getLocalTaskByShareToken(token);
    if (!task || !task.workspacePath) return null;
    return { workspacePath: task.workspacePath };
  }

  // 個別サブタスクの活動ログ（共有・read-only）
  app.get('/api/shared/:token/subtasks/:jobId/activity', async (req: Request, res: Response) => {
    try {
      const shared = await resolveSharedSubJob(req.params.token);
      if (!shared) { res.status(404).json({ error: 'Not found' }); return; }

      const job = await repo.getJob(req.params.jobId);
      if (!job || !job.worktreePath) { res.status(404).json({ error: 'Subtask not found' }); return; }
      // 封じ込め: 共有タスクの workspace 配下のサブジョブのみ。
      if (!isJobWithinWorkspace(shared.workspacePath, job.worktreePath)) {
        res.status(404).json({ error: 'Subtask not found' }); return;
      }

      const logPath = join(job.worktreePath, 'logs', 'activity.log');
      const activityLog = existsSync(logPath) ? readFileSync(logPath, 'utf-8') : '';
      res.json({ activityLog });
    } catch (err) {
      logger.error(`Shared subtask activity API error: ${err}`);
      res.status(500).json({ error: 'Failed to fetch subtask activity' });
    }
  });

  // 個別サブタスクのファイル一覧（共有・read-only）。listing は wildcard より先に登録。
  app.get('/api/shared/:token/subtasks/:jobId/files', async (req: Request, res: Response) => {
    try {
      const shared = await resolveSharedSubJob(req.params.token);
      if (!shared) { res.status(404).json({ error: 'Not found' }); return; }

      const job = await repo.getJob(req.params.jobId);
      if (!job || !job.worktreePath) { res.status(404).json({ error: 'Subtask not found' }); return; }
      if (!isJobWithinWorkspace(shared.workspacePath, job.worktreePath)) {
        res.status(404).json({ error: 'Subtask not found' }); return;
      }

      const base = resolve(job.worktreePath);
      const categories: Record<string, string[]> = {};
      for (const dir of ['output', 'logs', 'input']) {
        const dirPath = resolve(base, dir);
        if (!existsSync(dirPath)) continue;
        const dirFiles = readdirSync(dirPath, { recursive: true })
          .map((f) => String(f))
          .filter((f) => !statSync(resolve(dirPath, f)).isDirectory());
        if (dirFiles.length > 0) categories[dir] = dirFiles;
      }
      res.json({ files: categories['output'] ?? [], categories });
    } catch (err) {
      logger.error(`Shared subtask files API error: ${err}`);
      res.status(500).json({ error: 'Failed to list subtask files' });
    }
  });

  // 個別サブタスクのファイル本体（共有・read-only）
  app.get('/api/shared/:token/subtasks/:jobId/files/*', async (req: Request, res: Response) => {
    try {
      const shared = await resolveSharedSubJob(req.params.token);
      if (!shared) { res.status(404).json({ error: 'Not found' }); return; }

      const job = await repo.getJob(req.params.jobId);
      if (!job || !job.worktreePath) { res.status(404).json({ error: 'Subtask not found' }); return; }
      if (!isJobWithinWorkspace(shared.workspacePath, job.worktreePath)) {
        res.status(404).json({ error: 'Subtask not found' }); return;
      }

      const base = resolve(job.worktreePath);
      const resolved = resolve(base, req.params[0]);
      // 末尾セパレータ必須で sibling（<base>-x 等）の prefix 一致を弾く。base 自体は許可。
      if (resolved !== base && !resolved.startsWith(base + sep)) {
        res.status(403).json({ error: 'Access denied' }); return;
      }
      if (!existsSync(resolved)) { res.status(404).json({ error: 'File not found' }); return; }

      const stat = statSync(resolved);
      if (stat.isDirectory()) { res.json({ files: readdirSync(resolved) }); return; }

      setUntrustedFileResponseHeaders(res);
      res.sendFile(resolved);
    } catch (err) {
      logger.error(`Shared subtask file API error: ${err}`);
      res.status(500).json({ error: 'Failed to fetch subtask file' });
    }
  });

  // ── 認証付きエンドポイント ──

  app.post('/api/local/tasks/:taskId/share', express.json(), async (req: Request, res: Response) => {
    try {
      const taskId = parseTaskId(req.params.taskId);
      if (taskId === null) { res.status(400).json({ error: 'Invalid task ID' }); return; }
      const viewer = req.user as Express.User | undefined;
      const task = await repo.getLocalTask(taskId, viewer ? { viewer } : undefined);
      if (!checkTaskOwnership(req, res, task)) return;

      const shareToken = await repo.shareLocalTask(taskId);
      res.json({ shareToken, shareUrl: `/ui/shared/${shareToken}` });
    } catch (err) {
      logger.error(`Share task API error: ${err}`);
      res.status(500).json({ error: 'Failed to share task' });
    }
  });

  app.delete('/api/local/tasks/:taskId/share', async (req: Request, res: Response) => {
    try {
      const taskId = parseTaskId(req.params.taskId);
      if (taskId === null) { res.status(400).json({ error: 'Invalid task ID' }); return; }
      const viewer = req.user as Express.User | undefined;
      const task = await repo.getLocalTask(taskId, viewer ? { viewer } : undefined);
      if (!checkTaskOwnership(req, res, task)) return;

      await repo.unshareLocalTask(taskId);
      res.json({ ok: true });
    } catch (err) {
      logger.error(`Unshare task API error: ${err}`);
      res.status(500).json({ error: 'Failed to unshare task' });
    }
  });
}
