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

const PHONE_REGEX = /(\+?\d{1,3}[\s.-]?)?\(?\d{2,4}\)?[\s.-]?\d{3,4}[\s.-]?\d{3,4}/g;
const EMAIL_REGEX = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
const EMAIL_BLOCKLIST = [
  "users.noreply.github.com",
  "noreply.github.com",
  "example.com",
  "email.com",
  "yourcompany.com",
  "domain.com",
  "test.com"
];

export class ContactHuntingAdapter {
  constructor({ rootDir }) {
    this.rootDir = rootDir;
    this.python = resolveScraplingPython(rootDir);
    this.scriptPath = path.join(rootDir, "tools", "hermes_scrapling_enrich.py");
    this.scraplingAvailable = existsSync(this.scriptPath);
    this.hunterKey = process.env.HUNTER_API_KEY || "";
  }

  async enrichOwnerBundle(bundle, { run, sourceLog = [] } = {}) {
    const angles = [];
    const emails = new Set((bundle.emails || []).map((e) => e.toLowerCase()));
    let phones = new Set();
    let summaryText = "";
    let websiteText = "";

    if (bundle.owner?.email) emails.add(bundle.owner.email.toLowerCase());

    if (bundle.owner?.website) {
      const siteEmails = await this.scrapeWebsiteEmails(bundle.owner.website, sourceLog);
      siteEmails.forEach((e) => emails.add(e.toLowerCase()));
      angles.push(`website:${bundle.owner.website}`);
      websiteText = await this.fetchWebsiteText(bundle.owner.website, sourceLog);
      const sitePhones = extractPhones(websiteText);
      sitePhones.forEach((p) => phones.add(p));
    }

    if (bundle.owner?.blog && bundle.owner.blog !== bundle.owner?.website) {
      const blogEmails = await this.scrapeWebsiteEmails(bundle.owner.blog, sourceLog);
      blogEmails.forEach((e) => emails.add(e.toLowerCase()));
      angles.push(`blog:${bundle.owner.blog}`);
    }

    if (bundle.owner?.company) {
      angles.push(`company:${bundle.owner.company}`);
    }

    if (bundle.owner?.twitter) {
      angles.push(`twitter:${bundle.owner.twitter}`);
    }

    const hunterGuess = this.hunterKey
      ? await this.guessEmailWithHunter(bundle, sourceLog)
      : null;
    if (hunterGuess) {
      emails.add(hunterGuess.toLowerCase());
      angles.push("hunter-io");
    }

    const summary = await this.buildSummary({ bundle, websiteText, run });
    const cleanedEmails = [...emails].filter(isPlausibleEmail);
    const cleanedPhones = [...phones].filter(isPlausiblePhone);

    return {
      bundle,
      angles,
      emails: cleanedEmails,
      phones: cleanedPhones,
      summary
    };
  }

  async scrapeWebsiteEmails(url, sourceLog) {
    if (!url || !this.scraplingAvailable) return [];
    try {
      const result = await runJsonProcess(this.python, [this.scriptPath], {
        url,
        mode: "simple",
        timeout: 25
      });
      sourceLog.push({
        tool: "scrapling",
        url,
        ok: Boolean(result.ok),
        foundEmails: result.emails?.length || 0,
        foundPhones: result.phones?.length || 0
      });
      if (!result.ok && result.mode !== "dynamic") {
        const dynamic = await runJsonProcess(this.python, [this.scriptPath], {
          url,
          mode: "dynamic",
          timeout: 40
        });
        sourceLog.push({
          tool: "scrapling-dynamic",
          url,
          ok: Boolean(dynamic.ok),
          foundEmails: dynamic.emails?.length || 0,
          foundPhones: dynamic.phones?.length || 0
        });
        return [...(result.emails || []), ...(dynamic.emails || [])];
      }
      return result.emails || [];
    } catch (error) {
      sourceLog.push({ tool: "scrapling", url, ok: false, error: error.message });
      return [];
    }
  }

  async fetchWebsiteText(url, sourceLog) {
    if (!url || !this.scraplingAvailable) return "";
    try {
      const result = await runJsonProcess(this.python, [this.scriptPath], {
        url,
        mode: "simple",
        timeout: 25
      });
      const text = result.ok ? result.text || "" : "";
      sourceLog.push({
        tool: "scrapling-text",
        url,
        ok: Boolean(result.ok),
        bytes: text.length
      });
      return text;
    } catch (error) {
      sourceLog.push({ tool: "scrapling-text", url, ok: false, error: error.message });
      return "";
    }
  }

  async guessEmailWithHunter(bundle, sourceLog) {
    if (!bundle.owner?.login || !bundle.owner?.name) return null;
    const domain = extractDomain(bundle.owner.website || bundle.owner.blog || "");
    if (!domain) return null;
    try {
      const params = new URLSearchParams({
        domain,
        first_name: bundle.owner.name.split(/\s+/)[0] || "",
        last_name: bundle.owner.name.split(/\s+/).slice(1).join(" ") || ""
      });
      const response = await fetch(
        `https://api.hunter.io/v2/email-finder?${params.toString()}`,
        { headers: { "x-api-key": this.hunterKey } }
      );
      if (!response.ok) {
        sourceLog.push({ tool: "hunter", ok: false, status: response.status });
        return null;
      }
      const data = await response.json();
      const email = data?.data?.email;
      sourceLog.push({ tool: "hunter", ok: Boolean(email), email });
      return email || null;
    } catch (error) {
      sourceLog.push({ tool: "hunter", ok: false, error: error.message });
      return null;
    }
  }

  async buildSummary({ bundle, websiteText, run }) {
    const owner = bundle.owner || {};
    const repo = bundle.repo || {};
    const fallback = buildFallbackSummary({ bundle });

    if (!this.minimaxConfigured()) return fallback;
    try {
      const response = await this.minimaxChat({
        system:
          "You are a research analyst writing 2-line portfolio summaries about GitHub repo owners for a B2B sales team. " +
          "Be concrete: what they build, who they likely sell to, and any traction signal you can see. " +
          "If the data is thin, say so plainly. Return compact JSON only.",
        user: JSON.stringify({
          task: "Write a 2-sentence portfolio summary about the owner of a GitHub repo.",
          owner,
          repo,
          websiteTextSample: String(websiteText || "").slice(0, 1500),
          outputSchema: {
            summary: "two sentences max",
            likelyBusiness: "one short phrase",
            tractionSignal: "stars/forks/recency interpretation"
          }
        }),
        fallback
      });
      return response;
    } catch (error) {
      return fallback;
    }
  }

  minimaxConfigured() {
    return Boolean(process.env.MINIMAX_API_KEY);
  }

  async minimaxChat({ system, user, fallback }) {
    const apiKey = process.env.MINIMAX_API_KEY;
    const baseUrl = (process.env.MINIMAX_BASE_URL || "https://api.minimax.io/v1").replace(/\/$/, "");
    const model = process.env.MINIMAX_MODEL || "MiniMax-M3";
    if (!apiKey) return fallback;
    try {
      const response = await fetch(`${baseUrl}/chat/completions`, {
        method: "POST",
        headers: {
          authorization: `Bearer ${apiKey}`,
          "content-type": "application/json"
        },
        body: JSON.stringify({
          model,
          temperature: 0.2,
          messages: [
            { role: "system", content: `${system}\nReturn compact JSON only.` },
            { role: "user", content: user }
          ]
        })
      });
      if (!response.ok) return fallback;
      const data = await response.json();
      const content = data?.choices?.[0]?.message?.content || "";
      return parseJsonFromText(content, fallback);
    } catch {
      return fallback;
    }
  }
}

function extractPhones(text) {
  const matches = String(text || "").match(PHONE_REGEX) || [];
  return matches.map((m) => m.trim()).filter(Boolean);
}

function extractDomain(url) {
  const text = String(url || "").trim();
  if (!text) return "";
  try {
    return new URL(text.startsWith("http") ? text : `https://${text}`).hostname.replace(/^www\./, "");
  } catch {
    return "";
  }
}

function isPlausibleEmail(value) {
  const email = String(value || "").toLowerCase().trim();
  if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) return false;
  if (EMAIL_BLOCKLIST.some((b) => email.endsWith(b))) return false;
  if (email.includes("noreply")) return false;
  if (email.startsWith(".") || email.endsWith(".")) return false;
  return true;
}

function isPlausiblePhone(value) {
  const digits = String(value || "").replace(/\D/g, "");
  return digits.length >= 7 && digits.length <= 15;
}

function buildFallbackSummary({ bundle }) {
  const owner = bundle.owner || {};
  const repo = bundle.repo || {};
  const ownerName = owner.name || owner.login || "Unknown";
  const platform = bundle.platformLabel || bundle.platform;
  const lang = repo.language || "code";
  const stars = repo.stars || 0;
  const pushed = repo.pushedAt ? new Date(repo.pushedAt).toISOString().slice(0, 10) : "unknown";
  return {
    summary: `${ownerName} maintains ${repo.fullName || "a GitHub repo"} (${lang}, ${stars} stars, last push ${pushed}) which scrapes ${platform}. Likely an independent developer or small team selling scraping services or running it as a side project.`,
    likelyBusiness: `${platform} scraping tool`,
    tractionSignal: `${stars} stars, last activity ${pushed}`
  };
}

function parseJsonFromText(text, fallback) {
  try {
    return JSON.parse(text);
  } catch {
    const match = text.match(/\{[\s\S]*\}/);
    if (!match) return fallback;
    try {
      return JSON.parse(match[0]);
    } catch {
      return fallback;
    }
  }
}

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

    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 || "enrichment failed", emails: [], phones: [], text: "" });
      }
    });
    child.on("error", (error) => {
      clearTimeout(timer);
      resolve({ ok: false, error: error.message, emails: [], phones: [], text: "" });
    });
    child.stdin.end(JSON.stringify(payload));
  });
}
