import http from "node:http";
import { readFile, writeFile } from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { HermesAgent } from "./src/hermes.js";
import { leadExportFilename, leadsCsv } from "./src/export.js";
import { MiniMaxClient } from "./src/minimax.js";
import { OwnerRabbitHoleAdapter } from "./src/adapters/ownerRabbitHole.js";
import { LinkedInToolsAdapter } from "./src/adapters/linkedinTools.js";
import { OwnerEvidenceToolsAdapter } from "./src/adapters/ownerEvidenceTools.js";
import { FlowWatchdog } from "./src/flowWatchdog.js";
import { loadEnv, sendJson, serveStatic, readJsonBody } from "./src/utils.js";
import { Storage } from "./src/storage.js";
import { createRunWorkflow } from "./src/workflow.js";
import { repairPlaybook } from "./src/repairPlaybook.js";
import { EmailCampaignService } from "./src/campaigns.js";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const envPath = path.join(__dirname, ".env");
loadEnv(envPath);

const port = Number(process.env.PORT || 8787);
const host = process.env.HOST || "0.0.0.0";
const publicToken = process.env.HERMES_PUBLIC_TOKEN || "";
const storage = new Storage(path.join(__dirname, "data"));
await storage.init();
const campaigns = new EmailCampaignService({
  storage,
  publicBaseUrl: process.env.HERMES_PUBLIC_BASE_URL || `http://127.0.0.1:${port}`
});

const agent = new HermesAgent({
  projectName: path.basename(__dirname),
  rootDir: __dirname,
  storage
});
if (process.env.HERMES_RECOVER_RUNS !== "0") {
  const recoveredRuns = await agent.recoverInterruptedRuns();
  if (recoveredRuns.length) {
    console.log(`Recovered ${recoveredRuns.length} interrupted Hermes run(s): ${recoveredRuns.join(", ")}`);
  }
} else {
  const interruptedRuns = await agent.markInterruptedRuns();
  if (interruptedRuns.length) {
    console.log(`Marked ${interruptedRuns.length} inactive Hermes run(s) as interrupted: ${interruptedRuns.join(", ")}`);
  }
}

const server = http.createServer(async (req, res) => {
  try {
    const url = new URL(req.url, `http://${req.headers.host}`);
    if (publicToken && url.pathname.startsWith("/api/") && !hasAccess(req, url)) {
      return sendJson(res, 401, { error: "Access token required" });
    }

    if (req.method === "GET" && url.pathname === "/api/health") {
      return sendJson(res, 200, {
        ok: true,
        project: agent.projectName,
        minimaxConfigured: Boolean(process.env.MINIMAX_API_KEY),
        accessControlled: Boolean(publicToken),
        activeRuns: agent.activeRunCount(),
        workflow: createRunWorkflow({ source: "lead-scraper" }),
        minimaxQueue: agent.minimax.metrics(),
        autoRepairTools: agent.autoRepairTools,
        selfHealing: {
          enabled: process.env.HERMES_SELF_HEAL_RUNS !== "0",
          repairPlaybook: {
            codingTools: repairPlaybook.codingTools.length,
            skills: repairPlaybook.skills.length,
            plugins: repairPlaybook.plugins.length,
            diagnosticRunbooks: repairPlaybook.diagnosticRunbooks.length,
            patchTemplates: repairPlaybook.patchTemplates.length,
            patchIdeas: repairPlaybook.patchIdeas.length,
            guardrails: repairPlaybook.guardrails.length
          }
        },
        initialStrategyEnabled: agent.initialStrategyEnabled,
        finalRecoveryEnabled: agent.finalRecoveryEnabled,
        stagnationPivotEnabled: agent.stagnationPivotEnabled,
        stagnationWindow: agent.stagnationWindow,
        tools: {
          googleMapsScrapper: agent.googleMaps.available(),
          scraplingEnricher: agent.scrapling.available(),
          businessContactEnricher: agent.ownerRabbitHole?.scrapling?.available?.() || false,
          publicProfileTools: sanitizedToolStatus(agent.linkedinTools?.available?.()),
          publicEvidenceToolkit: sanitizedToolStatus(agent.ownerEvidenceTools?.available?.())
        },
        emailCampaigns: campaigns.status()
      });
    }

    if (req.method === "GET" && url.pathname === "/api/workflow") {
      return sendJson(res, 200, createRunWorkflow({ source: "lead-scraper" }));
    }

    if (req.method === "GET" && url.pathname === "/api/portfolio-defaults") {
      return sendJson(res, 200, portfolioDefaults());
    }

    if (req.method === "GET" && url.pathname === "/api/repair-playbook") {
      return sendJson(res, 200, repairPlaybook);
    }

    if (req.method === "GET" && url.pathname === "/api/settings") {
      return sendJson(res, 200, settingsStatus());
    }

    if (req.method === "POST" && url.pathname === "/api/settings/provider") {
      const body = await readJsonBody(req);
      const updates = {};
      if (body.apiKey) updates.MINIMAX_API_KEY = String(body.apiKey).trim();
      if (body.baseUrl) updates.MINIMAX_BASE_URL = String(body.baseUrl).trim();
      if (body.model) updates.MINIMAX_MODEL = String(body.model).trim();
      if (!Object.keys(updates).length) return sendJson(res, 400, { error: "No provider changes submitted" });
      await updateEnvFile(updates);
      Object.assign(process.env, updates);
      agent.minimax = new MiniMaxClient();
      agent.linkedinTools = new LinkedInToolsAdapter({ rootDir: __dirname, scrapling: agent.scrapling });
      agent.ownerEvidenceTools = new OwnerEvidenceToolsAdapter({ rootDir: __dirname });
      agent.ownerRabbitHole = new OwnerRabbitHoleAdapter({
        scrapling: agent.scrapling,
        minimax: agent.minimax,
        linkedinTools: agent.linkedinTools,
        ownerEvidenceTools: agent.ownerEvidenceTools
      });
      agent.flowWatchdog = new FlowWatchdog({ minimax: agent.minimax });
      return sendJson(res, 200, settingsStatus());
    }

    if (req.method === "GET" && url.pathname === "/api/runs") {
      return sendJson(res, 200, await storage.listRuns());
    }

    if (req.method === "GET" && url.pathname === "/api/campaigns") {
      return sendJson(res, 200, await campaigns.listCampaigns());
    }

    if (req.method === "POST" && url.pathname === "/api/campaigns") {
      const body = await readJsonBody(req);
      const run = await storage.getRun(body.runId || "");
      const campaign = await campaigns.createDraft(run, body);
      return sendJson(res, 201, campaign);
    }

    const campaignMatch = url.pathname.match(/^\/api\/campaigns\/([^/]+)$/);
    if (req.method === "GET" && campaignMatch) {
      const campaign = await campaigns.getCampaign(campaignMatch[1]);
      return campaign ? sendJson(res, 200, campaign) : sendJson(res, 404, { error: "Campaign not found" });
    }

    const campaignSendMatch = url.pathname.match(/^\/api\/campaigns\/([^/]+)\/send$/);
    if (req.method === "POST" && campaignSendMatch) {
      const campaign = await campaigns.sendCampaign(campaignSendMatch[1]);
      return campaign ? sendJson(res, 202, campaign) : sendJson(res, 404, { error: "Campaign not found" });
    }

    const campaignEventMatch = url.pathname.match(/^\/api\/campaigns\/([^/]+)\/events$/);
    if (req.method === "POST" && campaignEventMatch) {
      const campaign = await campaigns.recordEvent(campaignEventMatch[1], await readJsonBody(req));
      return campaign ? sendJson(res, 200, campaign) : sendJson(res, 404, { error: "Campaign not found" });
    }

    if (req.method === "GET" && url.pathname === "/api/unsubscribe") {
      const email = url.searchParams.get("email") || "";
      const result = await campaigns.unsubscribe(email, "unsubscribe_link");
      return result ? sendJson(res, 200, { ok: true }) : sendJson(res, 400, { error: "Email required" });
    }

    if (req.method === "POST" && url.pathname === "/api/runs") {
      const body = await readJsonBody(req);
      const run = await agent.startRun(applyPortfolioDefaults(body));
      return sendJson(res, 202, run);
    }

    const runMatch = url.pathname.match(/^\/api\/runs\/([^/]+)$/);
    if (req.method === "GET" && runMatch) {
      const run = await storage.getRun(runMatch[1]);
      return run ? sendJson(res, 200, run) : sendJson(res, 404, { error: "Run not found" });
    }

    const exportMatch = url.pathname.match(/^\/api\/runs\/([^/]+)\/leads\.csv$/);
    if (req.method === "GET" && exportMatch) {
      const run = await storage.getRun(exportMatch[1]);
      if (!run) return sendJson(res, 404, { error: "Run not found" });
      const filename = leadExportFilename(run);
      const approvedOnly = url.searchParams.get("approved") === "1";
      const includeExcluded = url.searchParams.get("includeExcluded") === "1";
      res.writeHead(200, {
        "content-type": "text/csv; charset=utf-8",
        "content-disposition": `attachment; filename="${filename}"`,
        "cache-control": "no-store"
      });
      return res.end(leadsCsv(run, { approvedOnly, includeExcluded }));
    }

    const leadMatch = url.pathname.match(/^\/api\/runs\/([^/]+)\/leads\/(\d+)$/);
    if (req.method === "PATCH" && leadMatch) {
      const run = await storage.getRun(leadMatch[1]);
      if (!run) return sendJson(res, 404, { error: "Run not found" });
      const index = Number(leadMatch[2]);
      const lead = run.qualifiedLeads?.[index];
      if (!lead) return sendJson(res, 404, { error: "Lead not found" });
      const body = await readJsonBody(req);
      if ("approved" in body) lead.approved = Boolean(body.approved);
      if ("excluded" in body) lead.excluded = Boolean(body.excluded);
      run.updatedAt = new Date().toISOString();
      await storage.saveRun(run);
      return sendJson(res, 200, run);
    }

    const cancelMatch = url.pathname.match(/^\/api\/runs\/([^/]+)\/cancel$/);
    if (req.method === "POST" && cancelMatch) {
      const cancelled = await agent.cancelRun(cancelMatch[1]);
      return sendJson(res, cancelled ? 200 : 404, { cancelled });
    }

    const killMatch = url.pathname.match(/^\/api\/runs\/([^/]+)\/kill$/);
    if (req.method === "POST" && killMatch) {
      const run = await agent.killRun(killMatch[1]);
      return run ? sendJson(res, 200, run) : sendJson(res, 404, { error: "Run not found" });
    }

    const resumeMatch = url.pathname.match(/^\/api\/runs\/([^/]+)\/resume$/);
    if (req.method === "POST" && resumeMatch) {
      const run = await agent.resumeRun(resumeMatch[1]);
      return run ? sendJson(res, 202, run) : sendJson(res, 404, { error: "Run not found" });
    }

    const eventMatch = url.pathname.match(/^\/api\/runs\/([^/]+)\/events$/);
    if (req.method === "GET" && eventMatch) {
      return agent.subscribe(eventMatch[1], req, res);
    }

    if (req.method === "GET" && url.pathname === "/") {
      const html = await readFile(path.join(__dirname, "public", "index.html"), "utf8");
      res.writeHead(200, {
        "content-type": "text/html; charset=utf-8",
        "cache-control": "no-store, max-age=0"
      });
      return res.end(html);
    }

    return serveStatic(req, res, path.join(__dirname, "public"));
  } catch (error) {
    console.error(error);
    return sendJson(res, error.statusCode || 500, { error: error.message || "Internal error" });
  }
});

server.listen(port, host, () => {
  console.log(`Hermes ${agent.projectName} dashboard running at http://${host}:${port}`);
});

function hasAccess(req, url) {
  const headerToken = req.headers["x-hermes-token"];
  return headerToken === publicToken || url.searchParams.get("token") === publicToken;
}

function settingsStatus() {
  return {
    provider: {
      minimaxConfigured: Boolean(process.env.MINIMAX_API_KEY),
      baseUrlConfigured: Boolean(process.env.MINIMAX_BASE_URL),
      model: process.env.MINIMAX_MODEL || "MiniMax-M3",
      minimaxQueue: agent.minimax.metrics()
    },
    emailCampaigns: campaigns.status()
  };
}

function sanitizedToolStatus(status) {
  if (!status || typeof status !== "object") return { ok: false, count: 0 };
  const tools = Array.isArray(status.tools) ? status.tools : [];
  return {
    ok: Boolean(status.ok),
    count: tools.length,
    installed: tools.filter((tool) => tool?.installed).length
  };
}

const BUSINESS_CONTACT_CRITERIA = [
  "Collect public business contact information only: business name, website, public business email, phone number, contact form URL, social links, location, source/evidence URL, validation status, quality/contact confidence score, and campaign-ready export fields.",
  "Use Google Maps, business websites, directories, social pages, booking platforms, and public company pages.",
  "Do not look for owners, founders, managers, decision-makers, personal emails, personal phones, personal LinkedIn profiles, or owner proof."
].join(" ");

function portfolioDefaults() {
  return {
    source: "lead-scraper",
    strategyVersion: "business-contact-v2",
    niche: process.env.HERMES_PORTFOLIO_NICHE || "nail salon",
    country: process.env.HERMES_PORTFOLIO_COUNTRY || "United States",
    location: process.env.HERMES_PORTFOLIO_LOCATION || process.env.HERMES_PORTFOLIO_CITIES || "Chennai",
    cities: process.env.HERMES_PORTFOLIO_LOCATION || process.env.HERMES_PORTFOLIO_CITIES || "Chennai",
    allUsa: false,
    target: Number(process.env.HERMES_PORTFOLIO_TARGET || 5),
    minScore: Number(process.env.HERMES_PORTFOLIO_MIN_SCORE || 60),
    maxPlannedLocations: Number(process.env.HERMES_PORTFOLIO_MAX_PLANNED_LOCATIONS || process.env.HERMES_MAX_PLANNED_LOCATIONS || 100),
    contactScrapeTimeoutMs: Number(process.env.HERMES_CONTACT_SCRAPE_TIMEOUT_MS || process.env.HERMES_OWNER_HUNT_TIMEOUT_MS || 90000),
    criteria: process.env.HERMES_PORTFOLIO_CRITERIA || BUSINESS_CONTACT_CRITERIA
  };
}

function applyPortfolioDefaults(input = {}) {
  const defaults = portfolioDefaults();
  const body = input && typeof input === "object" ? input : {};
  const location = String(body.location || body.cities || body.city || defaults.location || defaults.cities).trim() || defaults.location;
  const country = String(body.country || body.countryScope || defaults.country || "United States").trim() || "United States";
  return {
    source: "lead-scraper",
    strategyVersion: "business-contact-v2",
    niche: String(body.niche || "").trim() || defaults.niche,
    location,
    country,
    countryScope: country,
    cities: location,
    allUsa: Boolean(body.allUsa) && country.toLowerCase() === "united states",
    target: body.target || defaults.target,
    minScore: body.minScore || defaults.minScore,
    maxPlannedLocations: body.maxPlannedLocations || defaults.maxPlannedLocations,
    contactScrapeTimeoutMs: body.contactScrapeTimeoutMs || defaults.contactScrapeTimeoutMs,
    criteria: String(body.criteria || "").trim() || defaults.criteria
  };
}

async function updateEnvFile(updates) {
  let text = "";
  try {
    text = await readFile(envPath, "utf8");
  } catch {
    text = "";
  }
  const lines = text ? text.split(/\r?\n/) : [];
  for (const [key, value] of Object.entries(updates)) {
    const safeValue = envValue(value);
    const index = lines.findIndex((line) => line.trim().startsWith(`${key}=`));
    if (index >= 0) lines[index] = `${key}=${safeValue}`;
    else lines.push(`${key}=${safeValue}`);
  }
  await writeFile(envPath, `${lines.filter((line, index) => line.trim() || index < lines.length - 1).join("\n")}\n`);
}

function envValue(value) {
  const text = String(value || "");
  return /^[A-Za-z0-9_./:@-]+$/.test(text) ? text : JSON.stringify(text);
}
