import path from 'node:path';
import { spawn } from 'node:child_process';
import { mkdir, writeFile, readFile } from 'node:fs/promises';
import { readBody, sendJSON } from '../utils/http.js';
import { adoUploadAttachment, adoRequest, parseWorkItemId } from '../services/ado-api.js';
import { appendUsageRecord } from '../services/usage.js';
import { generatePrototypeWithClaude, gatherProjectContext } from '../server/chrome-cdp.js';
import { CLAUDE_BIN } from '../server/job-runner.js';

export async function handle(req, res, url, ctx) {
  const { cwd } = ctx;

  // GET /api/prototype/project-files
  if (req.method === 'GET' && url.pathname === '/api/prototype/project-files') {
    try {
      const { readdir } = await import('node:fs/promises');
      const EXCLUDE = new Set(['node_modules', '.git', '.sdd', 'dist', 'build', '.next', 'out', 'coverage', '.cache', 'public', 'static', '__pycache__', '.turbo', '.vercel']);
      const STYLE_EXT = /\.(css|scss|sass|less)$/;
      const CODE_EXT = /\.(tsx|jsx|ts|js|vue|svelte)$/;
      const LAYOUT_NAMES = /^(layout|Layout|App|app|root|Root|_app|_document|default|Default)\./;
      const CSS_PRESELECT = ['globals', 'global', 'variables', 'theme', 'main', 'index'];
      const styleFiles = [], layoutFiles = [], componentFiles = [];
      async function scanDir(dir, depth) {
        if (depth > 5) return;
        let entries; try { entries = await readdir(path.join(cwd, dir), { withFileTypes: true }); } catch { return; }
        for (const e of entries) {
          const relPath = (dir ? dir + '/' : '') + e.name;
          if (e.isDirectory()) {
            if (!EXCLUDE.has(e.name) && !e.name.startsWith('.')) await scanDir(relPath, depth + 1);
          } else if (e.isFile()) {
            if (STYLE_EXT.test(e.name)) {
              styleFiles.push({ path: relPath, preselected: CSS_PRESELECT.some(k => e.name.toLowerCase().includes(k)) });
            } else if (CODE_EXT.test(e.name) && !/\.(test|spec)\./.test(e.name) && !e.name.startsWith('_')) {
              if (LAYOUT_NAMES.test(e.name)) layoutFiles.push({ path: relPath, preselected: true });
              else if (/component|ui|shared/i.test(dir)) componentFiles.push({ path: relPath, preselected: false });
            }
          }
        }
      }
      await scanDir('', 0);
      const groups = [];
      if (styleFiles.length) groups.push({ label: 'CSS / Estilos', files: styleFiles.slice(0, 30) });
      if (layoutFiles.length) groups.push({ label: 'Layouts', files: layoutFiles.slice(0, 10) });
      if (componentFiles.length) groups.push({ label: 'Componentes', files: componentFiles.slice(0, 40) });
      sendJSON(res, { groups });
    } catch (e) { sendJSON(res, { groups: [], error: e.message }); }
    return true;
  }

  // POST /api/prototype/stream  (SSE — streams Claude output chunk by chunk)
  if (req.method === 'POST' && url.pathname === '/api/prototype/stream') {
    let body; try { body = JSON.parse(await readBody(req)); } catch { sendJSON(res, { error: 'bad request' }, 400); return true; }
    const { specName, stepId, selectedFiles } = body;
    if (!specName) { sendJSON(res, { error: 'specName obrigatório' }, 400); return true; }
    res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive' });
    const sse = (obj) => { try { res.write(`data: ${JSON.stringify(obj)}\n\n`); } catch {} };
    try {
      const specDir = path.join(cwd, '.sdd', 'specs', specName);
      const fileMap = { requirements: ['requirements.md'], design: ['requirements.md', 'design.md'], tasks: ['requirements.md', 'design.md', 'tasks.md'] };
      const files = fileMap[stepId || 'requirements'] || ['requirements.md'];
      const contexts = [], sourceFiles = [];
      for (const f of files) {
        try {
          const content = await readFile(path.join(cwd, '.sdd', 'specs', specName, f), 'utf8');
          if (content.trim().length > 20) {
            sourceFiles.push({ name: f, content });
            const trunc = content.length > 4000 ? content.slice(0, 4000) + '\n...[truncado]' : content;
            contexts.push(`### ${f}\n\`\`\`\n${trunc}\n\`\`\``);
          }
        } catch {}
      }
      if (!contexts.length) { sse({ error: 'Nenhum arquivo de spec encontrado. Execute as etapas anteriores primeiro.' }); res.end(); return true; }
      let styleSection;
      if (selectedFiles && selectedFiles.length > 0) {
        let pkgInfo = '';
        try {
          const pkg = JSON.parse(await readFile(path.join(cwd, 'package.json'), 'utf8'));
          const deps = { ...pkg.dependencies, ...pkg.devDependencies };
          const libs = [];
          if (deps.next) libs.push('Next.js'); else if (deps.react) libs.push('React');
          if (deps.vue || deps.nuxt) libs.push(deps.nuxt ? 'Nuxt.js' : 'Vue.js');
          if (deps['@angular/core']) libs.push('Angular');
          if (deps.tailwindcss) libs.push('Tailwind CSS');
          pkgInfo = libs.length ? 'Stack: ' + libs.join(', ') + '\n' : '';
        } catch {}
        const hasTailwind = selectedFiles.some(f => f.includes('tailwind'));
        const selectedContents = [];
        let totalLen = 0;
        for (const fp of selectedFiles) {
          if (totalLen > 8000) break;
          try {
            const content = await readFile(path.join(cwd, fp), 'utf8');
            const trunc = content.length > 2000 ? content.slice(0, 2000) + '\n...[truncado]' : content;
            selectedContents.push(`### ${fp}\n\`\`\`\n${trunc}\n\`\`\``);
            totalLen += trunc.length;
          } catch {}
        }
        styleSection = selectedContents.length
          ? `## Estilo visual do projeto — OBRIGATÓRIO replicar\n${pkgInfo}Use os arquivos abaixo como única fonte de verdade visual. Replique EXATAMENTE: cores, tipografia, border-radius, sombras, espaçamentos, padrão de botões, inputs e cards.${hasTailwind ? '\nO projeto usa Tailwind CSS: aplique as classes utilitárias diretamente no HTML.' : '\nConverta os estilos para CSS inline equivalente no <style>.'}\nNÃO use estilos genéricos. NÃO use frameworks externos. O resultado deve parecer pertencer ao mesmo sistema visual.\n\n${selectedContents.join('\n\n')}`
          : `## Estilo visual\nUse um visual limpo e neutro: fundo #f8f9fa, cards #fff com borda #e5e7eb, accent #4f46e5, tipografia system-ui.`;
      } else {
        const projectCtx = await gatherProjectContext(cwd);
        const hasCtx = projectCtx.files.length > 0;
        styleSection = hasCtx
          ? `## Estilo visual do projeto — OBRIGATÓRIO replicar\n${projectCtx.pkgInfo ? projectCtx.pkgInfo + '\n' : ''}Use os arquivos abaixo como única fonte de verdade visual. Replique EXATAMENTE: cores, tipografia, border-radius, sombras, espaçamentos, padrão de botões, inputs e cards.${projectCtx.files.some(f => f.path.includes('tailwind')) ? '\nO projeto usa Tailwind CSS: aplique as classes utilitárias diretamente no HTML.' : '\nConverta os estilos para CSS inline equivalente no <style>.'}\nNÃO use estilos genéricos. NÃO use frameworks externos.\n\n${projectCtx.files.map(f => `### ${f.path}\n\`\`\`\n${f.content}\n\`\`\``).join('\n\n')}`
          : `## Estilo visual\nUse um visual limpo e neutro: fundo #f8f9fa, cards #fff com borda #e5e7eb, accent #4f46e5, tipografia system-ui. Mantenha o protótipo simples — sem inventar identidade visual que não existe nas specs.`;
      }
      const prompt = `Você é um desenvolvedor frontend sênior gerando um protótipo de tela para validação com stakeholders.\n\n## Especificações da feature\n\n${contexts.join('\n\n')}\n\n${styleSection}\n\n## REGRAS DE ESCOPO — CRÍTICO\n- Prototipe SOMENTE o que está explicitamente descrito nas especificações acima\n- NÃO invente fluxos, seções, funcionalidades ou telas não mencionadas\n- NÃO adicione navegação global, menus, headers ou footers a menos que estejam nas specs\n- Se a spec descreve um formulário, entregue apenas aquele formulário — sem extras\n\n## Implementação\n- Use HTML, CSS no <style> e JavaScript vanilla no <script> — tudo inline, sem dependências externas\n- Dados fictícios mas realistas\n- Inclua apenas os estados descritos nas specs (ex: erro, sucesso, vazio) — não invente outros\n- Seja responsivo (max-width: 960px centralizado)\n- NÃO use imagens externas, fontes externas nem CDNs\n\nResponda APENAS com o código HTML completo, começando com <!DOCTYPE html> e terminando com </html>.`;
      const proc = spawn(CLAUDE_BIN, ['--print', '--dangerously-skip-permissions', prompt], { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
      let fullOutput = '';
      proc.stdout.on('data', d => { const chunk = d.toString(); fullOutput += chunk; sse({ chunk, total: fullOutput.length }); });
      proc.on('close', async () => {
        appendUsageRecord('prototype', prompt, fullOutput, cwd).catch(() => {});
        const htmlMatch = fullOutput.match(/<!DOCTYPE html[\s\S]*<\/html>/i);
        const html = htmlMatch ? htmlMatch[0] : fullOutput.trim();
        if (html && html.length >= 200) {
          try { await mkdir(specDir, { recursive: true }); await writeFile(path.join(specDir, `prototype-${stepId || 'requirements'}.html`), html, 'utf8'); } catch {}
        }
        sse({ done: true, html: html && html.length >= 200 ? html : null, sourceFiles });
        res.end();
      });
      proc.on('error', (err) => { sse({ error: err.message }); res.end(); });
      req.on('close', () => { try { proc.kill(); } catch {} });
      setTimeout(() => { try { proc.kill(); } catch {} }, 120000);
    } catch (e) { sse({ error: e.message }); res.end(); }
    return true;
  }

  // POST /api/prototype/generate
  if (req.method === 'POST' && url.pathname === '/api/prototype/generate') {
    try {
      const { specName, stepId } = JSON.parse(await readBody(req));
      if (!specName) { sendJSON(res, { error: 'specName obrigatório' }, 400); return true; }
      const { html, sourceFiles } = await generatePrototypeWithClaude(specName, stepId || 'requirements', cwd);
      sendJSON(res, { html, sourceFiles });
    } catch (e) { sendJSON(res, { error: e.message }, 500); }
    return true;
  }

  // POST /api/prototype/send-to-ado
  if (req.method === 'POST' && url.pathname === '/api/prototype/send-to-ado') {
    try {
      const { specName, stepId, workItemId, adoConfig, html: htmlDirect, fileName: fileNameDirect } = JSON.parse(await readBody(req));
      if (!workItemId || !adoConfig?.orgUrl || !adoConfig?.project || !adoConfig?.pat) {
        sendJSON(res, { error: 'Campos obrigatórios: workItemId, adoConfig (orgUrl, project, pat)' }, 400); return true;
      }
      let html, fileName;
      if (htmlDirect) {
        html = htmlDirect;
        fileName = fileNameDirect || 'relatorio.html';
      } else {
        if (!specName) { sendJSON(res, { error: 'Informe specName ou html' }, 400); return true; }
        const htmlPath = path.join(cwd, '.sdd', 'specs', specName, `prototype-${stepId || 'requirements'}.html`);
        html = await readFile(htmlPath, 'utf8').catch(() => null);
        if (!html) { sendJSON(res, { error: 'Arquivo de protótipo não encontrado. Gere o protótipo primeiro.' }, 404); return true; }
        fileName = `prototype-${specName}-${stepId || 'requirements'}.html`;
      }
      const orgUrl = adoConfig.orgUrl.endsWith('/') ? adoConfig.orgUrl.slice(0, -1) : adoConfig.orgUrl;
      const wid = parseWorkItemId(String(workItemId));
      if (!wid) { sendJSON(res, { error: 'Work Item ID inválido' }, 400); return true; }

      // Fetch existing attachments to avoid duplicate names
      const wiRes = await adoRequest(orgUrl, adoConfig.pat, 'GET',
        `${orgUrl}/${encodeURIComponent(adoConfig.project)}/_apis/wit/workitems/${wid}?$expand=relations&api-version=7.1`
      );
      const existingNames = new Set(
        (wiRes.body?.relations || [])
          .filter(r => r.rel === 'AttachedFile')
          .map(r => (r.attributes?.name || r.url?.split('/').pop() || '').toLowerCase())
      );

      // Generate unique filename
      const dot = fileName.lastIndexOf('.');
      const base = dot >= 0 ? fileName.slice(0, dot) : fileName;
      const ext = dot >= 0 ? fileName.slice(dot) : '';
      let uniqueName = fileName;
      let counter = 2;
      while (existingNames.has(uniqueName.toLowerCase())) {
        uniqueName = `${base}-${counter}${ext}`;
        counter++;
      }

      const attachRes = await adoUploadAttachment(orgUrl, adoConfig.project, adoConfig.pat, uniqueName, html);
      if (attachRes.status !== 201 && attachRes.status !== 200) {
        const msg = attachRes.body?.message || attachRes.body?.value?.Message || JSON.stringify(attachRes.body);
        sendJSON(res, { error: `Erro no upload: ${msg}` }, 500); return true;
      }
      const linkRes = await adoRequest(orgUrl, adoConfig.pat, 'PATCH',
        `${orgUrl}/${encodeURIComponent(adoConfig.project)}/_apis/wit/workitems/${wid}?api-version=7.1`,
        [{ op: 'add', path: '/relations/-', value: { rel: 'AttachedFile', url: attachRes.body.url, attributes: { comment: 'Protótipo UX — SDD-FP' } } }]
      );
      if (linkRes.status !== 200) {
        const msg = linkRes.body?.message || linkRes.body?.value?.Message || JSON.stringify(linkRes.body);
        sendJSON(res, { error: `Upload feito mas erro ao vincular: ${msg}` }, 500); return true;
      }
      sendJSON(res, { ok: true, attachUrl: attachRes.body.url, fileName: uniqueName });
    } catch (e) { sendJSON(res, { error: e.message }, 500); }
    return true;
  }

  return false;
}
