#!/usr/bin/env node

const TERMINAL_STATUSES = new Set(["complete", "partial", "failed", "cancelled", "killed", "interrupted"]);

async function main() {
  const args = parseArgs(process.argv.slice(2));
  const baseUrl = String(args["base-url"] || "http://localhost:8787").replace(/\/$/, "");
  const target = numberArg(args.target, 100);
  const minDiscovered = numberArg(args["min-discovered"], target);
  const minOwnerHunts = numberArg(args["min-owner-hunts"], Math.min(25, target));
  const minOwnerContacts = numberArg(args["min-owner-contacts"], 1);
  const minOwnerAgents = numberArg(args["min-owner-agents"], minOwnerHunts);
  const minOwnerPages = numberArg(args["min-owner-pages"], minOwnerHunts);
  const timeoutMs = numberArg(args["timeout-ms"], 30 * 60 * 1000);
  const pollMs = numberArg(args["poll-ms"], 10000);
  const startedAt = Date.now();
  let runId = args["run-id"] || "";

  if (!runId) {
    const payload = {
      source: "google-maps",
      niche: args.niche || "salons",
      cities: args.cities || "Austin, TX",
      target,
      minScore: numberArg(args["min-score"], 60),
      leadWorkerConcurrency: numberArg(args["lead-workers"], 3),
      ownerAgentSwarmSize: numberArg(args["owner-agents"], 50),
      ownerHuntTimeoutMs: numberArg(args["owner-hunt-timeout-ms"], 0) || undefined,
      criteria: args.criteria || "Use Google Maps and the business website only as seed evidence. Identify the business owner, find their public social profile, then qualify only when an owner email or owner phone is found from owner-level evidence."
    };
    const started = await requestJson(`${baseUrl}/api/runs`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify(payload)
    });
    runId = started.id;
  }

  let run = await requestJson(`${baseUrl}/api/runs/${encodeURIComponent(runId)}`);
  while (!TERMINAL_STATUSES.has(run.status) && Date.now() - startedAt < timeoutMs) {
    await sleep(pollMs);
    run = await requestJson(`${baseUrl}/api/runs/${encodeURIComponent(runId)}`);
  }

  const timedOut = !TERMINAL_STATUSES.has(run.status);
  const cleanup = await cleanupTimedOutRun({ baseUrl, runId, timedOut, args });
  if (cleanup?.run) run = cleanup.run;
  const csvAudit = await auditCsv(baseUrl, runId).catch((error) => ({
    ok: false,
    error: error.message || String(error),
    rowCount: 0,
    hasOwnerColumns: false
  }));
  const audit = buildAudit({
    run,
    baseUrl,
    runId,
    timedOut,
    cleanup,
    elapsedMs: Date.now() - startedAt,
    criteria: { target, minDiscovered, minOwnerHunts, minOwnerContacts, minOwnerAgents, minOwnerPages },
    csvAudit
  });

  process.stdout.write(`${JSON.stringify(audit, null, 2)}\n`);
  process.exitCode = audit.pass ? 0 : 1;
}

function buildAudit({ run, baseUrl, runId, timedOut, cleanup, elapsedMs, criteria, csvAudit }) {
  const counters = run.counters || {};
  const scaleAudit = run.scaleAudit || {};
  const qualified = (run.qualifiedLeads || []).filter((lead) => !lead.excluded).length;
  const discovered = Number(scaleAudit.discovered ?? run.discoveredLeads?.length ?? counters.discovered ?? 0);
  const ownerHunts = Number(scaleAudit.ownerHunts ?? counters.ownerRabbitHoles ?? 0);
  const ownerAgentsStarted = Number(scaleAudit.ownerAgentsStarted ?? counters.ownerAgentsStarted ?? 0);
  const ownerAgentsDeployed = Number(scaleAudit.ownerAgentsDeployed ?? counters.ownerAgentsDeployed ?? 0);
  const ownerAgentsForCheck = Math.max(ownerAgentsStarted, ownerAgentsDeployed);
  const ownerPages = Number(scaleAudit.ownerAgentPagesFetched ?? counters.ownerAgentPagesFetched ?? 0);
  const ownerContacts = Number(scaleAudit.ownerContacts ?? counters.ownerContactsFound ?? 0);
  const targetValue = Number(run.input?.target || criteria.target);
  const checks = {
    terminal: TERMINAL_STATUSES.has(run.status),
    notTimedOut: !timedOut,
    targetReached: qualified >= targetValue,
    backendHundredsScale: targetValue >= 100,
    discoveredAtScale: discovered >= criteria.minDiscovered,
    ownerHuntsPresent: ownerHunts >= criteria.minOwnerHunts,
    ownerAgentsDeployed: ownerAgentsForCheck >= criteria.minOwnerAgents,
    ownerPagesFetched: ownerPages >= criteria.minOwnerPages,
    ownerContactsFound: ownerContacts >= criteria.minOwnerContacts,
    portfolioCycleClosed: Boolean(scaleAudit.portfolioCycleClosed),
    finalCsvReady: csvAudit.ok && csvAudit.rowCount > 0 && csvAudit.hasOwnerColumns
  };
  const pass = Object.values(checks).every(Boolean);
  return {
    pass,
    checks,
    runId,
    runUrl: `${baseUrl}/api/runs/${encodeURIComponent(runId)}`,
    status: run.status,
    elapsedMs,
    cleanup,
    criteria,
    metrics: {
      target: targetValue,
      qualified,
      discovered,
      pendingDiscovered: Number(scaleAudit.pendingDiscovered || 0),
      processedDiscovered: Number(scaleAudit.processedDiscovered || 0),
      ownerBacklogDrained: Boolean(scaleAudit.ownerBacklogDrained),
      ownerHunts,
      ownerAgentsStarted,
      ownerAgentsDeployed,
      ownerAgentsTimedOut: Number(scaleAudit.ownerAgentsTimedOut || counters.ownerAgentsTimedOut || 0),
      ownerAgentsPerHunt: Number(scaleAudit.ownerAgentsPerHunt || 0),
      ownerPagesFetched: ownerPages,
      ownerProfiles: Number(scaleAudit.ownerProfiles || counters.ownerProfilesFound || 0),
      ownerContacts,
      ownerContactYieldPercent: Number(scaleAudit.ownerContactYieldPercent || 0),
      llmCalls: Number(scaleAudit.llmCalls || counters.llmCalls || 0),
      llmTimeouts: Number(scaleAudit.llmTimeouts || counters.llmTimeouts || 0),
      ownerHuntTimeoutMs: Number(scaleAudit.ownerHuntTimeoutMs || run.input?.ownerHuntTimeoutMs || 0),
      ownerHuntTimeouts: Number(scaleAudit.ownerHuntTimeouts || counters.ownerHuntTimeouts || 0),
      plannedLocations: Number(scaleAudit.plannedLocations || 0),
      exhaustedLocations: Number(scaleAudit.exhaustedLocations || 0),
      maxPlannedLocations: Number(scaleAudit.maxPlannedLocations || 0),
      portfolioStopReason: scaleAudit.portfolioStopReason || "",
      failures: run.failures?.length || 0
    },
    csv: csvAudit,
    latestFailures: (run.failures || []).slice(-5).map((failure) => ({
      stage: failure.stage,
      message: failure.message
    }))
  };
}

async function auditCsv(baseUrl, runId) {
  const response = await fetch(`${baseUrl}/api/runs/${encodeURIComponent(runId)}/leads.csv?approved=1`);
  if (!response.ok) throw new Error(`CSV export failed with ${response.status}`);
  const text = await response.text();
  const lines = text.trim().split(/\r?\n/).filter(Boolean);
  const header = lines[0] || "";
  return {
    ok: true,
    rowCount: Math.max(0, lines.length - 1),
    hasOwnerColumns: /Owner Name/i.test(header) && /Owner Email/i.test(header) && /Owner Phone/i.test(header),
    bytes: Buffer.byteLength(text)
  };
}

async function cleanupTimedOutRun({ baseUrl, runId, timedOut, args }) {
  if (!timedOut) return { attempted: false };
  const action = args["kill-on-timeout"] ? "kill" : args["cancel-on-timeout"] ? "cancel" : "";
  if (!action) return { attempted: false };
  try {
    await requestJson(`${baseUrl}/api/runs/${encodeURIComponent(runId)}/${action}`, { method: "POST" });
    const run = await requestJson(`${baseUrl}/api/runs/${encodeURIComponent(runId)}`);
    return { attempted: true, action, ok: true, run };
  } catch (error) {
    return { attempted: true, action, ok: false, error: error.message || String(error) };
  }
}

async function requestJson(url, options = {}) {
  const response = await fetch(url, options);
  const text = await response.text();
  if (!response.ok) {
    throw new Error(`${options.method || "GET"} ${url} failed with ${response.status}: ${text.slice(0, 500)}`);
  }
  return text ? JSON.parse(text) : {};
}

function parseArgs(argv) {
  const out = {};
  for (let index = 0; index < argv.length; index += 1) {
    const arg = argv[index];
    if (!arg.startsWith("--")) continue;
    const key = arg.slice(2);
    const next = argv[index + 1];
    if (!next || next.startsWith("--")) {
      out[key] = true;
    } else {
      out[key] = next;
      index += 1;
    }
  }
  return out;
}

function numberArg(value, fallback) {
  const number = Number(value);
  return Number.isFinite(number) && number >= 0 ? number : fallback;
}

function sleep(ms) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

main().catch((error) => {
  process.stderr.write(`${error.stack || error.message || String(error)}\n`);
  process.exitCode = 1;
});
