import { readBody, sendJSON } from '../utils/http.js';
import { adoRequest } from '../services/ado-api.js';
import { isJiraOrg, parseJiraOrg, parseJiraAuth, jiraDiscoverFieldIds, jiraQueryByCodes } from '../services/jira-api.js';

const JIRA_DEFAULT_FIELD = 'Intake Code';
// Cache: `${baseUrl}:${fieldName}` → fieldIds[]  (avoids rediscovering on every query)
const _jiraFieldCache = new Map();

async function resolveJiraFieldIds(baseUrl, auth, fieldName) {
  const key = `${baseUrl}:${fieldName}`;
  if (_jiraFieldCache.has(key)) return _jiraFieldCache.get(key);
  const ids = await jiraDiscoverFieldIds(baseUrl, auth, fieldName);
  if (ids.length) _jiraFieldCache.set(key, ids);
  return ids;
}

export async function handle(req, res, url, ctx) {
  if (req.method === 'POST' && url.pathname === '/api/intake/query') {
    try {
      const { codes, orgs, pat: defaultPat } = JSON.parse(await readBody(req));
      if (!codes?.length || !orgs?.length) {
        sendJSON(res, { error: 'Campos obrigatórios: codes, orgs' }, 400); return true;
      }

      res.writeHead(200, {
        'Content-Type': 'text/event-stream',
        'Cache-Control': 'no-cache',
        'Connection': 'keep-alive',
      });
      const sse = (data) => res.write('data: ' + JSON.stringify(data) + '\n\n');

      const BATCH = 20;
      const FETCH  = 200;
      const startTime = Date.now();
      let completed = 0;

      sse({ type: 'start', total: orgs.length });

      const orgResults = await Promise.all(orgs.map(async (orgItem) => {
        const orgName = typeof orgItem === 'string' ? orgItem : orgItem.name;
        const pat     = (typeof orgItem === 'object' && orgItem.pat) ? orgItem.pat : defaultPat;

        // ── helpers ──────────────────────────────────────────────────────────
        const elapsed      = (since) => Date.now() - since;
        const estimatedRem = () => {
          const avg = (Date.now() - startTime) / completed;
          return Math.max(0, (orgs.length - completed) * avg);
        };
        const orgDone = (orgStart, found, error) => {
          sse({ type: 'org_done', org: orgName, found, error: error || null,
                elapsed_ms: elapsed(orgStart), completed, total: orgs.length,
                estimated_remaining_ms: estimatedRem() });
        };

        // ── JIRA ─────────────────────────────────────────────────────────────
        if (isJiraOrg(orgName)) {
          const { baseUrl, projectKeys } = parseJiraOrg(orgName);
          const auth = parseJiraAuth(pat);
          const orgStart = Date.now();
          sse({ type: 'org_start', org: orgName });

          if (!auth.token) {
            completed++;
            orgDone(orgStart, 0, 'Nenhuma credencial configurada para esta org Jira');
            return { org: orgName, found: 0, matches: new Map(), error: 'Sem credencial' };
          }

          try {
            const fieldIds = await resolveJiraFieldIds(baseUrl, auth, JIRA_DEFAULT_FIELD);
            if (!fieldIds.length) {
              completed++;
              orgDone(orgStart, 0, `Campo "${JIRA_DEFAULT_FIELD}" não encontrado nesta instância Jira`);
              return { org: orgName, found: 0, matches: new Map(), error: `Campo "${JIRA_DEFAULT_FIELD}" não encontrado` };
            }

            const { error, matches } = await jiraQueryByCodes(baseUrl, auth, fieldIds, codes, projectKeys, BATCH);
            completed++;
            orgDone(orgStart, matches.size, error);
            return { org: orgName, found: matches.size, matches, error };
          } catch (e) {
            completed++;
            orgDone(orgStart, 0, e.message || 'Erro desconhecido');
            return { org: orgName, found: 0, matches: new Map(), error: e.message };
          }
        }

        // ── ADO ──────────────────────────────────────────────────────────────
        if (!pat) {
          completed++;
          sse({ type: 'org_done', org: orgName, found: 0, error: 'Nenhum PAT configurado para esta org',
                elapsed_ms: 0, completed, total: orgs.length, estimated_remaining_ms: 0 });
          return { org: orgName, found: 0, matches: new Map(), error: 'Nenhum PAT configurado' };
        }

        let orgPath = orgName.trim();
        if (orgPath.startsWith('https://') || orgPath.startsWith('http://')) {
          try { const u = new URL(orgPath); orgPath = u.pathname.split('/').filter(Boolean).slice(0, 2).join('/'); }
          catch { orgPath = orgPath.replace(/^https?:\/\/[^/]+\//, ''); }
        }
        const base    = 'https://dev.azure.com/' + orgPath.split('/').map(s => encodeURIComponent(s.trim())).filter(Boolean).join('/');
        const wiqlUrl = `${base}/_apis/wit/wiql?api-version=7.1`;
        const allIds  = new Set();
        const orgStart = Date.now();
        const wiqlErrors = [];
        sse({ type: 'org_start', org: orgName });

        try {
          for (let i = 0; i < codes.length; i += BATCH) {
            const batch    = codes.slice(i, i + BATCH);
            const tagConds = batch.map(c => `[System.Tags] Contains '${c.replace(/'/g, "''")}'`).join(' OR ');
            const r = await adoRequest(base, pat, 'POST', wiqlUrl, {
              query: `SELECT [System.Id] FROM WorkItems WHERE (${tagConds})`,
              top: 20000,
            });
            if (r.status === 200 && Array.isArray(r.body.workItems)) {
              r.body.workItems.forEach(wi => allIds.add(wi.id));
            } else if (r.status === 401 || r.status === 403) {
              completed++;
              orgDone(orgStart, 0, `HTTP ${r.status} (PAT inválido ou sem acesso)`);
              return { org: orgName, found: 0, matches: new Map(), error: `HTTP ${r.status} (PAT inválido ou sem acesso)` };
            } else {
              const msg = typeof r.body === 'object' ? (r.body?.message || JSON.stringify(r.body).slice(0, 100)) : String(r.body).slice(0, 100);
              wiqlErrors.push(`Batch ${i / BATCH + 1}: HTTP ${r.status}${msg ? ' — ' + msg : ''}`);
            }
          }

          const ids = [...allIds];
          const wiDetails = [];
          for (let i = 0; i < ids.length; i += FETCH) {
            const idChunk  = ids.slice(i, i + FETCH).join(',');
            const fields   = encodeURIComponent('System.Id,System.Title,System.State,System.Tags,System.WorkItemType');
            const itemsUrl = `${base}/_apis/wit/workitems?ids=${idChunk}&fields=${fields}&api-version=7.1`;
            const r = await adoRequest(base, pat, 'GET', itemsUrl, null);
            if (r.status === 200 && Array.isArray(r.body.value)) wiDetails.push(...r.body.value);
          }

          const matches = new Map();
          for (const wi of wiDetails) {
            const tags = (wi.fields?.['System.Tags'] || '').split(';').map(t => t.trim()).filter(Boolean);
            for (const code of codes) {
              if (tags.some(t => t.toLowerCase() === code.toLowerCase())) {
                if (!matches.has(code)) {
                  matches.set(code, { found: true, org: orgName, state: wi.fields?.['System.State'] || '',
                    wiId: wi.id, wiTitle: wi.fields?.['System.Title'] || '', wiType: wi.fields?.['System.WorkItemType'] || '', wiCount: 0 });
                }
                matches.get(code).wiCount++;
              }
            }
          }

          completed++;
          const wiqlWarn = wiqlErrors.length ? wiqlErrors.join('; ') : null;
          orgDone(orgStart, matches.size, wiqlWarn);
          return { org: orgName, found: matches.size, matches, error: wiqlWarn };
        } catch (e) {
          completed++;
          const msg = e.message?.includes('401') ? 'HTTP 401 (PAT inválido ou sem acesso)' : (e.message || 'Erro desconhecido');
          orgDone(orgStart, 0, msg);
          return { org: orgName, found: 0, matches: new Map(), error: msg };
        }
      }));

      // ── Aggregate results ─────────────────────────────────────────────────
      const codeMap = new Map();
      const codeSeenOrgs = new Map(); // code → Set of org identifiers already counted
      for (const orgResult of orgResults) {
        for (const [code, match] of orgResult.matches) {
          if (!codeMap.has(code)) {
            codeMap.set(code, { ...match });
            codeSeenOrgs.set(code, new Set([match.org]));
          } else {
            const seenOrgs = codeSeenOrgs.get(code);
            if (!seenOrgs.has(match.org)) {
              seenOrgs.add(match.org);
              codeMap.get(code).wiCount = (codeMap.get(code).wiCount || 0) + (match.wiCount || 0);
            }
            // Same org already counted — skip to avoid double-counting duplicate entries
          }
        }
      }
      const results = codes.map(code => {
        const m = codeMap.get(code);
        return m ? { code, ...m } : { code, found: false };
      });
      const found = results.filter(r => r.found).length;
      const byOrg = {};
      orgResults.forEach(o => { byOrg[o.org] = { found: o.found, error: o.error }; });

      sse({ type: 'done', total: codes.length, found, byOrg, results });
      res.end();
    } catch (e) { try { res.end(); } catch (_) {} }
    return true;
  }

  return false;
}
