import { existsSync } from "node:fs";
import path from "node:path";
import { resolveOsintPython } from "../pythonRuntime.js";
import { processSupervisor } from "../processSupervisor.js";
import { normalizeText } from "../utils.js";

const TOOL_DEFS = [
  {
    id: "the_harvester",
    name: "laramies/theHarvester",
    path: "theHarvester",
    stage: "website_enrichment"
  },
  {
    id: "photon",
    name: "s0md3v/Photon",
    path: "Photon",
    stage: "website_enrichment"
  },
  {
    id: "maigret",
    name: "soxoj/maigret",
    path: "maigret",
    stage: "owner_profile_rabbit_hole"
  },
  {
    id: "sherlock",
    name: "sherlock-project/sherlock",
    path: "sherlock",
    stage: "owner_profile_rabbit_hole"
  },
  {
    id: "social_analyzer",
    name: "qeeqbox/social-analyzer",
    path: "social-analyzer",
    stage: "owner_profile_rabbit_hole"
  },
  {
    id: "reacher",
    name: "reacherhq/backend",
    path: "reacher",
    stage: "quality_gate"
  }
];

const TOOL_TIMEOUT_WINDOW = Math.max(5, Number(process.env.HERMES_OWNER_TOOL_TIMEOUT_WINDOW || 40));
const TOOL_TIMEOUT_SKIP_COUNT = Math.max(1, Number(process.env.HERMES_OWNER_TOOL_TIMEOUT_SKIP_COUNT || 3));
const TOOL_TIMEOUT_MIN_ATTEMPTS = Math.max(1, Number(process.env.HERMES_OWNER_TOOL_TIMEOUT_MIN_ATTEMPTS || 3));
const TOOL_TIMEOUT_SKIP_RATE = Math.max(0, Math.min(1, Number(process.env.HERMES_OWNER_TOOL_TIMEOUT_SKIP_RATE || 0.5)));

export class OwnerEvidenceToolsAdapter {
  constructor({ rootDir }) {
    this.rootDir = rootDir;
    this.vendorDir = path.resolve(rootDir, process.env.HERMES_OSINT_TOOLS_DIR || path.join("tools", "vendor"));
    this.python = resolveOsintPython(rootDir);
    this.scriptPath = path.join(rootDir, "tools", "hermes_owner_evidence_tools.py");
  }

  available() {
    const tools = this.configuredTools();
    return {
      ok: existsSync(this.scriptPath) && tools.some((tool) => tool.installed),
      script: this.scriptPath,
      python: this.python,
      vendorDir: this.vendorDir,
      tools
    };
  }

  configuredTools() {
    return TOOL_DEFS.map((tool) => ({
      ...tool,
      installed: existsSync(path.join(this.vendorDir, tool.path))
    }));
  }

  async enrichWebsite({ lead = {}, enrichment = {}, timeout = 30, run } = {}) {
    if (!this.available().ok || !lead.website) {
      return emptyResult("website", "owner evidence tools unavailable or missing website");
    }
    const policy = ownerEvidenceToolPolicy(run, { mode: "website" });
    return runJsonProcess(this.python, [this.scriptPath], {
      mode: "website",
      vendorDir: this.vendorDir,
      url: lead.website,
      businessName: lead.name || "",
      domain: hostFromUrl(lead.website),
      timeout,
      disabledTools: policy.disabledTools,
      toolPolicy: policy,
      seedEmails: enrichment.emails || [],
      seedPhones: enrichment.phones || []
    }, Math.max(12000, timeout * 1000 + 5000));
  }

  async findOwnerProfiles({ lead = {}, owner = {}, timeout = 45, run } = {}) {
    if (!this.available().ok) {
      return emptyResult("profiles", "owner evidence tools unavailable");
    }
    const usernames = candidateUsernames({ lead, owner });
    if (!usernames.length) return emptyResult("profiles", "no username candidates");
    const policy = ownerEvidenceToolPolicy(run, { mode: "profiles" });
    return runJsonProcess(this.python, [this.scriptPath], {
      mode: "profiles",
      vendorDir: this.vendorDir,
      lead: {
        name: lead.name || "",
        city: lead.city || "",
        website: lead.website || "",
        category: lead.category || ""
      },
      owner: {
        name: owner?.name || "",
        title: owner?.title || ""
      },
      usernames,
      timeout,
      disabledTools: policy.disabledTools,
      toolPolicy: policy
    }, Math.max(15000, timeout * 1000 + 10000));
  }
}

export function ownerEvidenceToolPolicy(run = {}, { mode = "" } = {}) {
  const rows = recentToolAttempts(run)
    .filter((attempt) => !mode || toolMode(attempt.tool) === mode)
    .slice(-TOOL_TIMEOUT_WINDOW);
  const stats = new Map();
  for (const attempt of rows) {
    if (!attempt?.tool || ["missing", "skipped"].includes(attempt.status)) continue;
    const entry = stats.get(attempt.tool) || {
      tool: attempt.tool,
      attempts: 0,
      failures: 0,
      timeouts: 0,
      successes: 0
    };
    entry.attempts += 1;
    if (attempt.status === "used") entry.successes += 1;
    if (attempt.status === "failed") entry.failures += 1;
    if (attempt.timeout) entry.timeouts += 1;
    stats.set(attempt.tool, entry);
  }

  const disabledTools = [];
  const reasons = {};
  for (const entry of stats.values()) {
    const timeoutRate = entry.attempts ? entry.timeouts / entry.attempts : 0;
    entry.timeoutRate = Math.round(timeoutRate * 1000) / 1000;
    if (
      entry.attempts >= TOOL_TIMEOUT_MIN_ATTEMPTS &&
      entry.timeouts >= TOOL_TIMEOUT_SKIP_COUNT &&
      timeoutRate >= TOOL_TIMEOUT_SKIP_RATE
    ) {
      disabledTools.push(entry.tool);
      reasons[entry.tool] = `${entry.timeouts}/${entry.attempts} recent attempts timed out; skipping this timeout-heavy owner-evidence tool and continuing with public-profile/Scrapling paths`;
    }
  }

  return {
    mode,
    action: disabledTools.length ? "skip-timeout-heavy-tools" : "use-installed-tools",
    disabledTools,
    reasons,
    stats: [...stats.values()]
  };
}

function recentToolAttempts(run = {}) {
  const attempts = Array.isArray(run.ownerEvidenceToolAttempts) ? run.ownerEvidenceToolAttempts : [];
  return attempts.flatMap((entry) => Array.isArray(entry.toolAttempts) ? entry.toolAttempts : []);
}

function toolMode(toolName = "") {
  const def = TOOL_DEFS.find((tool) => tool.name === toolName || tool.id === toolName);
  if (!def) return "";
  if (def.stage === "website_enrichment") return "website";
  if (def.stage === "owner_profile_rabbit_hole") return "profiles";
  return def.stage;
}

function emptyResult(mode, reason) {
  return {
    ok: false,
    mode,
    skipped: true,
    reason,
    links: [],
    profileLinks: [],
    emails: [],
    phones: [],
    evidence: [],
    toolAttempts: []
  };
}

function candidateUsernames({ lead = {}, owner = {} }) {
  const seeds = new Set();
  const ownerName = normalizeText(owner.name || "");
  const businessName = normalizeText(lead.name || "");
  const domain = hostFromUrl(lead.website || "");
  if (ownerName) {
    const parts = ownerName.toLowerCase().replace(/[^a-z0-9\s._-]+/g, " ").split(/\s+/).filter(Boolean);
    if (parts.length >= 2) {
      seeds.add(`${parts[0]}${parts.at(-1)}`);
      seeds.add(`${parts[0]}.${parts.at(-1)}`);
      seeds.add(`${parts[0]}_${parts.at(-1)}`);
      seeds.add(`${parts[0][0]}${parts.at(-1)}`);
    }
    if (parts.length === 1) seeds.add(parts[0]);
  }
  if (businessName) {
    const slug = businessName.toLowerCase().replace(/[^a-z0-9]+/g, "").slice(0, 32);
    if (slug.length >= 4) seeds.add(slug);
  }
  if (domain) {
    const host = domain.split(".")[0].replace(/[^a-z0-9._-]+/gi, "").toLowerCase();
    if (host.length >= 4) seeds.add(host);
  }
  return [...seeds].filter((value) => value.length >= 3 && value.length <= 40).slice(0, 8);
}

function hostFromUrl(url) {
  try {
    return new URL(String(url || "").startsWith("http") ? url : `https://${url}`).hostname.replace(/^www\./, "");
  } catch {
    return "";
  }
}

function runJsonProcess(command, args, payload, timeoutMs) {
  return new Promise((resolve) => {
    const child = processSupervisor.spawn(command, args, {
      stdio: ["pipe", "pipe", "pipe"],
      detached: process.platform !== "win32"
    }, { label: "owner-evidence-tools", killGroup: true });
    let stdout = "";
    let stderr = "";
    const timer = setTimeout(() => {
      processSupervisor.killChild(child, "SIGTERM");
      stderr += "\nTimed out";
      setTimeout(() => processSupervisor.killChild(child, "SIGKILL"), 5000).unref?.();
    }, timeoutMs);

    child.stdout.on("data", (chunk) => { stdout += chunk.toString(); });
    child.stderr.on("data", (chunk) => { stderr += chunk.toString(); });
    child.on("close", () => {
      clearTimeout(timer);
      try {
        const jsonLine = stdout.trim().split(/\r?\n/).reverse().find((line) => line.trim().startsWith("{"));
        resolve(JSON.parse(jsonLine || stdout));
      } catch {
        resolve({ ok: false, error: stderr || stdout || "owner evidence tools failed", links: [], profileLinks: [], emails: [], phones: [], evidence: [] });
      }
    });
    child.on("error", (error) => {
      clearTimeout(timer);
      resolve({ ok: false, error: error.message, links: [], profileLinks: [], emails: [], phones: [], evidence: [] });
    });
    child.stdin.end(JSON.stringify(payload));
  });
}
