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

const JIRA_DEFAULT_FIELD = 'Intake Code';
const JIRA_FIELD_FALLBACKS = ['Intake Code', 'BC ID', 'Business Case ID', 'labels'];
// Cache: `${baseUrl}` → fieldIds[]  (avoids rediscovering on every query)
const _jiraFieldCache = new Map();

async function resolveJiraFieldIds(baseUrl, auth) {
  if (_jiraFieldCache.has(baseUrl)) return _jiraFieldCache.get(baseUrl);
  for (const name of JIRA_FIELD_FALLBACKS) {
    const ids = await jiraDiscoverFieldIds(baseUrl, auth, name);
    if (ids.length) {
      _jiraFieldCache.set(baseUrl, ids);
      return ids;
    }
  }
  return [];
}

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);
            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;
  }

  // ── POST /api/intake/metrics (SSE) ───────────────────────────────────────
  if (req.method === 'POST' && url.pathname === '/api/intake/metrics') {
    try {
      const { orgs, pat: defaultPat } = JSON.parse(await readBody(req));
      if (!orgs?.length) { sendJSON(res, { error: 'Nenhuma org informada' }, 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 FIELDS = [
        'System.Id', 'System.State',
        'Microsoft.VSTS.Common.StateChangeDate',
        'Microsoft.VSTS.Common.ActivatedDate',
        'System.CreatedDate',
        'System.IterationPath',
        'System.AssignedTo',
        'System.Title',
      ].join(',');
      const FETCH = 200;

      const adoOrgs = orgs.filter(o => !isJiraOrg(typeof o === 'string' ? o : o.name));
      sse({ type: 'start', total: orgs.length, totalOrgs: orgs.length });

      let completed = 0;
      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;

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

          if (!auth.token) {
            completed++;
            sse({ type: 'org_done', org: orgName, itemCount: 0, error: 'Nenhuma credencial configurada para esta org Jira', completed, total: orgs.length });
            return { org: orgName, found: 0, items: [], error: 'Sem credencial' };
          }

          try {
            const { error, items } = await jiraQueryEpics(baseUrl, auth, projectKeys);
            completed++;
            sse({ type: 'org_done', org: orgName, itemCount: items.length, error: error || null, completed, total: orgs.length });
            return { org: orgName, items, total: items.length, error };
          } catch (e) {
            completed++;
            sse({ type: 'org_done', org: orgName, itemCount: 0, error: e.message || 'Erro desconhecido', completed, total: orgs.length });
            return { org: orgName, items: [], total: 0, error: e.message };
          }
        }

        sse({ type: 'org_start', org: orgName });

        if (!pat) {
          completed++;
          sse({ type: 'org_done', org: orgName, itemCount: 0, error: 'Nenhum PAT configurado', completed, total: orgs.length });
          return { org: orgName, error: 'Nenhum PAT configurado para esta org', items: [], total: 0 };
        }

        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`;

        try {
          const wiql = await adoRequest(base, pat, 'POST', wiqlUrl, {
            query: `SELECT [System.Id] FROM WorkItems WHERE [System.WorkItemType]='Epic' ORDER BY [System.ChangedDate] DESC`,
            top: 1000,
          });

          if (wiql.status === 401 || wiql.status === 403) {
            completed++;
            const err = `HTTP ${wiql.status} (PAT inválido ou sem acesso)`;
            sse({ type: 'org_done', org: orgName, itemCount: 0, error: err, completed, total: orgs.length });
            return { org: orgName, error: err, items: [], total: 0 };
          }
          if (wiql.status !== 200 || !Array.isArray(wiql.body?.workItems)) {
            const msg = typeof wiql.body === 'object' ? (wiql.body?.message || JSON.stringify(wiql.body).slice(0, 100)) : String(wiql.body).slice(0, 100);
            const err = `WIQL HTTP ${wiql.status}${msg ? ' — ' + msg : ''}`;
            completed++;
            sse({ type: 'org_done', org: orgName, itemCount: 0, error: err, completed, total: orgs.length });
            return { org: orgName, error: err, items: [], total: 0 };
          }

          const ids = wiql.body.workItems.map(w => w.id);
          if (!ids.length) {
            completed++;
            sse({ type: 'org_done', org: orgName, itemCount: 0, error: null, completed, total: orgs.length });
            return { org: orgName, items: [], total: 0 };
          }

          const chunks = [];
          for (let i = 0; i < ids.length; i += FETCH) chunks.push(ids.slice(i, i + FETCH));

          const chunkResults = await Promise.all(chunks.map(chunk => {
            const itemsUrl = `${base}/_apis/wit/workitems?ids=${chunk.join(',')}&fields=${encodeURIComponent(FIELDS)}&api-version=7.1`;
            return adoRequest(base, pat, 'GET', itemsUrl, null);
          }));

          const items = [];
          for (const r of chunkResults) {
            if (r.status === 200 && Array.isArray(r.body?.value)) {
              for (const wi of r.body.value) {
                const rawAssigned = wi.fields?.['System.AssignedTo'];
                const assignedTo  = typeof rawAssigned === 'object' && rawAssigned !== null
                  ? (rawAssigned.displayName || rawAssigned.uniqueName || '')
                  : (rawAssigned || '');
                items.push({
                  id:            wi.id,
                  title:         wi.fields?.['System.Title'] || '',
                  state:         wi.fields?.['System.State'] || '',
                  stateDate:     wi.fields?.['Microsoft.VSTS.Common.StateChangeDate'] || null,
                  activatedDate: wi.fields?.['Microsoft.VSTS.Common.ActivatedDate'] || null,
                  createdDate:   wi.fields?.['System.CreatedDate'] || null,
                  iterationPath: wi.fields?.['System.IterationPath'] || '',
                  assignedTo:    assignedTo,
                });
              }
            }
          }

          completed++;
          sse({ type: 'org_done', org: orgName, itemCount: items.length, error: null, completed, total: orgs.length });
          return { org: orgName, items, total: ids.length };
        } catch (e) {
          completed++;
          sse({ type: 'org_done', org: orgName, itemCount: 0, error: e.message || 'Erro desconhecido', completed, total: orgs.length });
          return { org: orgName, error: e.message, items: [], total: 0 };
        }
      }));

      sse({ type: 'done', orgResults });
      res.end();
    } catch (e) { try { res.end(); } catch (_) {} }
    return true;
  }

  return false;
}
