import https from 'node:https';
import http from 'node:http';

export function isJiraOrg(name) {
  const n = (name || '').trim().toLowerCase();
  return n.startsWith('jira:') || n.includes('.atlassian.net');
}

export function parseJiraOrg(orgName) {
  let raw = orgName.trim().replace(/^jira:/i, '');
  if (!raw.startsWith('http')) raw = 'https://' + raw;
  try {
    const u = new URL(raw);
    const parts = u.pathname.split('/').filter(Boolean);
    let projectKeys = [];
    if (parts.length > 0 && parts[0].toLowerCase() !== 'browse') {
      // /PROJ or /PROJ1,PROJ2 — explicit project filter
      projectKeys = parts[0].split(',').map(k => k.trim().toUpperCase()).filter(Boolean);
    }
    // /browse/PROJ-123 is a Jira UI route — search all projects, no filter
    return { baseUrl: u.protocol + '//' + u.host, projectKeys, displayName: u.host };
  } catch {
    return { baseUrl: raw, projectKeys: [], displayName: raw };
  }
}

export function parseJiraAuth(pat) {
  if (!pat) return { email: null, token: null };
  // "email@domain.com:token" — split on first colon that comes after the @ sign
  const atIdx = pat.indexOf('@');
  const colonIdx = atIdx !== -1 ? pat.indexOf(':', atIdx) : pat.indexOf(':');
  if (colonIdx !== -1 && colonIdx < pat.length - 1) {
    return { email: pat.slice(0, colonIdx), token: pat.slice(colonIdx + 1) };
  }
  return { email: null, token: pat };
}

function authHeader(auth) {
  if (auth.email && auth.token) {
    return 'Basic ' + Buffer.from(`${auth.email}:${auth.token}`).toString('base64');
  }
  return 'Bearer ' + (auth.token || '');
}

export async function jiraRequest(baseUrl, auth, method, path, body) {
  const url = new URL(path.startsWith('http') ? path : baseUrl + path);
  const bodyStr = body ? JSON.stringify(body) : null;
  const options = {
    hostname: url.hostname,
    port: url.port || (url.protocol === 'https:' ? 443 : 80),
    path: url.pathname + url.search,
    method: method || 'GET',
    headers: {
      Authorization: authHeader(auth),
      Accept: 'application/json',
      ...(bodyStr ? { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(bodyStr) } : {}),
    },
  };
  return new Promise((resolve, reject) => {
    const lib = url.protocol === 'https:' ? https : http;
    const req = lib.request(options, (res) => {
      let data = '';
      res.on('data', d => { data += d; });
      res.on('end', () => {
        try { resolve({ status: res.statusCode, body: JSON.parse(data) }); }
        catch { resolve({ status: res.statusCode, body: data }); }
      });
    });
    req.on('error', reject);
    req.setTimeout(30000, () => { req.destroy(); reject(new Error('Jira request timeout')); });
    if (bodyStr) req.write(bodyStr);
    req.end();
  });
}

// Discovers ALL Jira custom field IDs matching the display name (e.g. "Intake Code" → ["customfield_10317","customfield_10350"])
// Multiple fields with the same name can exist when scoped to different projects.
// Special case: "labels" is a standard Jira field — always available, no discovery needed.
export async function jiraDiscoverFieldIds(baseUrl, auth, fieldName) {
  if (fieldName.toLowerCase().trim() === 'labels') return ['labels'];
  const r = await jiraRequest(baseUrl, auth, 'GET', '/rest/api/3/field', null);
  if (r.status !== 200 || !Array.isArray(r.body)) return [];
  const needle = fieldName.toLowerCase().trim();
  return r.body
    .filter(f => f.custom && f.name.toLowerCase().trim() === needle)
    .map(f => f.id);
}

// Fetches all Epics from a Jira instance for metrics purposes.
// Returns array of items normalized to match the ADO metrics shape.
export async function jiraQueryEpics(baseUrl, auth, projectKeys, maxResults = 1000) {
  const projectClause = projectKeys.length > 0
    ? ` AND project in (${projectKeys.map(k => `"${k}"`).join(',')})`
    : '';
  const jql = `issuetype = Epic${projectClause} ORDER BY created DESC`;
  const fields = ['summary', 'status', 'created', 'updated', 'resolutiondate', 'assignee'];

  const items = [];
  let nextPageToken;
  let fetched = 0;

  while (fetched < maxResults) {
    const body = { jql, maxResults: Math.min(100, maxResults - fetched), fields };
    if (nextPageToken) body.nextPageToken = nextPageToken;

    const r = await jiraRequest(baseUrl, auth, 'POST', '/rest/api/3/search/jql', body);

    if (r.status === 401 || r.status === 403) {
      return { error: `HTTP ${r.status} (credenciais inválidas ou sem acesso)`, items };
    }
    if (r.status !== 200 || !Array.isArray(r.body?.issues)) {
      const msg = typeof r.body === 'object'
        ? (r.body?.errorMessages?.[0] || r.body?.errors?.jql || JSON.stringify(r.body).slice(0, 120))
        : String(r.body).slice(0, 120);
      return { error: `HTTP ${r.status}${msg ? ' — ' + msg : ''}`, items };
    }

    for (const issue of r.body.issues) {
      const f = issue.fields || {};
      // Use resolutiondate if available (better state change approximation), else updated
      const stateDate = f.resolutiondate || f.updated || null;
      items.push({
        id:            issue.key,
        title:         f.summary || '',
        state:         f.status?.name || '',
        createdDate:   f.created || null,
        stateDate,
        activatedDate: null, // no direct Jira equivalent
        iterationPath: '',
        assignedTo:    f.assignee?.displayName || f.assignee?.emailAddress || '',
      });
    }

    fetched += r.body.issues.length;
    nextPageToken = r.body.nextPageToken;
    if (!nextPageToken || r.body.issues.length === 0) break;
  }

  return { error: null, items };
}

// Legacy single-field wrapper kept for backward compatibility
export async function jiraDiscoverFieldId(baseUrl, auth, fieldName) {
  const ids = await jiraDiscoverFieldIds(baseUrl, auth, fieldName);
  return ids[0] || null;
}

// Searches Jira issues across ALL fieldIds for any of the given codes (batched JQL).
// Returns { error, matches: Map<code, matchObj> }
export async function jiraQueryByCodes(baseUrl, auth, fieldIds, codes, projectKeys, batchSize = 20) {
  // Accept single string or array
  const ids = Array.isArray(fieldIds) ? fieldIds : [fieldIds];
  const matches = new Map();
  const projectClause = projectKeys.length > 0
    ? ` AND project in (${projectKeys.map(k => `"${k}"`).join(',')})`
    : '';

  for (let i = 0; i < codes.length; i += batchSize) {
    const batch = codes.slice(i, i + batchSize);
    const codeList = batch.map(c => `"${c.replace(/"/g, '\\"')}"`).join(',');
    // Build OR clause across all field IDs so one request covers every Intake Code variant
    // "labels" uses native JQL syntax; custom fields use cf[N] syntax
    const fieldClause = ids.map(id =>
      id === 'labels'
        ? `labels in (${codeList})`
        : `cf[${id.replace('customfield_', '')}] in (${codeList})`
    ).join(' OR ');
    const jql = `(${fieldClause})${projectClause} ORDER BY created DESC`;
    let nextPageToken = undefined;

    while (true) {
      const body = { jql, maxResults: 100, fields: ['summary', 'status', 'issuetype', ...ids] };
      if (nextPageToken) body.nextPageToken = nextPageToken;

      const r = await jiraRequest(baseUrl, auth, 'POST', '/rest/api/3/search/jql', body);

      if (r.status === 401 || r.status === 403) {
        return { error: `HTTP ${r.status} (credenciais inválidas ou sem acesso)`, matches };
      }
      if (r.status !== 200 || !Array.isArray(r.body?.issues)) {
        const msg = typeof r.body === 'object'
          ? (r.body?.errorMessages?.[0] || r.body?.errors?.jql || JSON.stringify(r.body).slice(0, 120))
          : String(r.body).slice(0, 120);
        return { error: `HTTP ${r.status}${msg ? ' — ' + msg : ''}`, matches };
      }

      for (const issue of r.body.issues) {
        // Check all field IDs for a matching code value
        for (const fieldId of ids) {
          const raw = issue.fields[fieldId];
          const issueCodes = (Array.isArray(raw) ? raw : [raw])
            .map(v => String(v ?? '').trim()).filter(Boolean);
          for (const issueCode of issueCodes) {
            const matched = codes.find(c => c.toLowerCase() === issueCode.toLowerCase());
            if (!matched) continue;
            if (!matches.has(matched)) {
              matches.set(matched, {
                found: true,
                org: baseUrl,
                state: issue.fields.status?.name || '',
                wiId: issue.key,
                wiTitle: issue.fields.summary || '',
                wiType: issue.fields.issuetype?.name || '',
                wiCount: 0,
              });
            }
            matches.get(matched).wiCount++;
          }
        }
      }

      nextPageToken = r.body.nextPageToken;
      if (!nextPageToken || r.body.issues.length === 0) break;
    }
  }
  return { error: null, matches };
}
