import { existsSync } from "node:fs";
import path from "node:path";
import { processSupervisor } from "../processSupervisor.js";

const TOOL_DEFS = [
  {
    id: "linkedin_scraper",
    name: "joeyism/linkedin_scraper",
    path: "linkedin_scraper"
  },
  {
    id: "jobspy",
    name: "speedyapply/JobSpy",
    path: "JobSpy"
  },
  {
    id: "linkedin_profile_scraper_api",
    name: "josephlimtech/linkedin-profile-scraper-api",
    path: "linkedin-profile-scraper-api"
  },
  {
    id: "py_linkedin_jobs_scraper",
    name: "spinlud/py-linkedin-jobs-scraper",
    path: "py-linkedin-jobs-scraper"
  },
  {
    id: "scrapling",
    name: "D4Vinci/Scrapling",
    path: "Scrapling"
  }
];

export class LinkedInToolsAdapter {
  constructor({ rootDir, scrapling }) {
    this.rootDir = rootDir;
    this.scrapling = scrapling;
    this.vendorDir = path.resolve(rootDir, process.env.HERMES_LINKEDIN_TOOLS_DIR || path.join("tools", "vendor"));
    this.profileApiScript = path.join(rootDir, "tools", "hermes_linkedin_profile_api.mjs");
    this.linkedinScraperScript = process.env.HERMES_LINKEDIN_SCRAPER_SCRIPT || path.join(rootDir, "tools", "hermes_linkedin_scraper_profile.py");
    this.jobSpyScript = process.env.HERMES_JOBSPY_LINKEDIN_SCRIPT || path.join(rootDir, "tools", "hermes_jobspy_linkedin_jobs.py");
    this.pyLinkedinJobsScript = process.env.HERMES_PY_LINKEDIN_JOBS_SCRIPT || path.join(rootDir, "tools", "hermes_py_linkedin_jobs_scraper.py");
    this.linkedinPython = process.env.HERMES_LINKEDIN_PYTHON || path.join(rootDir, ".venv-linkedin", "bin", "python");
  }

  available() {
    const tools = this.configuredTools();
    return {
      ok: Boolean(this.scrapling?.available?.()) && tools.some((tool) => tool.installed),
      vendorDir: this.vendorDir,
      tools
    };
  }

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

  async enrichProfile({ url, timeout = 28, lead = {}, owner = {} }) {
    const result = await this.scrapling.enrich({ url, mode: "simple", timeout });
    const linkedinScraperResult = await this.runLinkedinScraper({ url, timeout });
    if (linkedinScraperResult.ok) {
      result.emails = mergeArrays(result.emails, linkedinScraperResult.emails);
      result.phones = mergeArrays(result.phones, linkedinScraperResult.phones);
      result.text = [result.text, linkedinScraperResult.text].filter(Boolean).join("\n\n");
      result.linkedinScraperProfile = linkedinScraperResult.profile;
      result.ok = true;
    }
    const profileApiResult = await this.runLinkedinProfileApi({ url, timeout });
    if (profileApiResult.ok) {
      result.emails = mergeArrays(result.emails, profileApiResult.emails);
      result.phones = mergeArrays(result.phones, profileApiResult.phones);
      result.text = [result.text, profileApiResult.text].filter(Boolean).join("\n\n");
      result.profileApi = profileApiResult.profile;
      result.ok = true;
    }
    const jobSpyResult = await this.runJobSpy({ lead, owner, timeout });
    const pyLinkedinJobsResult = await this.runPyLinkedinJobs({ lead, owner, timeout });
    result.text = [result.text, jobSpyResult.text, pyLinkedinJobsResult.text].filter(Boolean).join("\n\n");
    result.linkedinJobEvidence = {
      jobSpy: jobSpyResult.jobs || [],
      pyLinkedinJobs: pyLinkedinJobsResult.jobs || []
    };
    const installedTools = this.configuredTools().filter((tool) => tool.installed);
    const toolAttempts = this.profileToolAttempts({ result, installedTools, linkedinScraperResult, profileApiResult, jobSpyResult, pyLinkedinJobsResult });
    return {
      ...result,
      toolchain: installedTools.map((tool) => tool.name),
      toolAttempts,
      profileToolSource: "public-profile-scrapling"
    };
  }

  async runJobSpy({ lead = {}, owner = {}, timeout }) {
    if (!existsSync(this.jobSpyScript)) return { ok: false, skipped: true, error: "JobSpy bridge missing" };
    if (looksLikePath(this.linkedinPython) && !existsSync(this.linkedinPython)) return { ok: false, skipped: true, error: "linkedin python runtime missing" };
    return runJsonProcess(this.linkedinPython, [this.jobSpyScript], {
      businessName: lead.name || "",
      ownerName: owner.name || "",
      city: lead.city || "",
      limit: Number(process.env.HERMES_LINKEDIN_JOB_LIMIT || 3)
    }, Math.max(8000, Number(timeout || 28) * 1000));
  }

  async runPyLinkedinJobs({ lead = {}, owner = {}, timeout }) {
    if (process.env.HERMES_ENABLE_PY_LINKEDIN_JOBS !== "1") {
      return { ok: false, skipped: true, error: "set HERMES_ENABLE_PY_LINKEDIN_JOBS=1 to enable browser job corroboration" };
    }
    if (!existsSync(this.pyLinkedinJobsScript)) return { ok: false, skipped: true, error: "py-linkedin-jobs-scraper bridge missing" };
    if (looksLikePath(this.linkedinPython) && !existsSync(this.linkedinPython)) return { ok: false, skipped: true, error: "linkedin python runtime missing" };
    return runJsonProcess(this.linkedinPython, [this.pyLinkedinJobsScript], {
      businessName: lead.name || "",
      ownerName: owner.name || "",
      city: lead.city || "",
      limit: Number(process.env.HERMES_LINKEDIN_JOB_LIMIT || 3),
      liAt: process.env.HERMES_LINKEDIN_LI_AT || ""
    }, Math.max(10000, Number(timeout || 28) * 1000 + 10000));
  }

  async runLinkedinScraper({ url, timeout }) {
    const hasSession = Boolean(process.env.HERMES_LINKEDIN_SESSION_JSON || process.env.HERMES_LINKEDIN_LI_AT);
    if (!hasSession) return { ok: false, skipped: true, error: "missing HERMES_LINKEDIN_SESSION_JSON or HERMES_LINKEDIN_LI_AT" };
    if (!existsSync(this.linkedinScraperScript)) return { ok: false, skipped: true, error: "linkedin_scraper bridge missing" };
    if (looksLikePath(this.linkedinPython) && !existsSync(this.linkedinPython)) return { ok: false, skipped: true, error: "linkedin python runtime missing" };
    return runJsonProcess(this.linkedinPython, [this.linkedinScraperScript], {
      url,
      timeout: Math.max(10000, Number(timeout || 28) * 1000),
      sessionPath: process.env.HERMES_LINKEDIN_SESSION_JSON || "",
      liAt: process.env.HERMES_LINKEDIN_LI_AT || ""
    }, Math.max(15000, Number(timeout || 28) * 1000 + 10000));
  }

  async runLinkedinProfileApi({ url, timeout }) {
    if (!process.env.HERMES_LINKEDIN_LI_AT) return { ok: false, skipped: true, error: "missing HERMES_LINKEDIN_LI_AT" };
    if (!existsSync(this.profileApiScript)) return { ok: false, skipped: true, error: "profile API bridge missing" };
    if (!existsSync(path.join(this.vendorDir, "linkedin-profile-scraper-api", "node_modules"))) {
      return { ok: false, skipped: true, error: "linkedin-profile-scraper-api dependencies missing" };
    }
    return runJsonProcess(process.execPath, [this.profileApiScript], {
      url,
      timeout: Math.max(10000, Number(timeout || 28) * 1000),
      vendorDir: path.join(this.vendorDir, "linkedin-profile-scraper-api"),
      sessionCookieValue: process.env.HERMES_LINKEDIN_LI_AT
    }, Math.max(15000, Number(timeout || 28) * 1000 + 5000));
  }

  profileToolAttempts({ result, installedTools, linkedinScraperResult, profileApiResult, jobSpyResult, pyLinkedinJobsResult }) {
    const installed = new Set(installedTools.map((tool) => tool.id));
    return TOOL_DEFS.map((tool) => {
      if (!installed.has(tool.id)) {
        return {
          tool: tool.name,
          status: "missing",
          role: toolRole(tool.id),
          extracted: {}
        };
      }

      if (tool.id === "scrapling") {
        return {
          tool: tool.name,
          status: result.ok ? "used" : "failed",
          role: toolRole(tool.id),
          extracted: {
            emails: result.emails?.length || 0,
            phones: result.phones?.length || 0,
            links: result.links?.length || 0,
            text: result.text ? result.text.length : 0
          },
          error: result.ok ? "" : result.error || "scrapling profile extraction failed"
        };
      }

      if (tool.id === "linkedin_scraper") {
        const hasSession = Boolean(process.env.HERMES_LINKEDIN_SESSION_JSON || process.env.HERMES_LINKEDIN_LI_AT);
        return {
          tool: tool.name,
          status: linkedinScraperResult?.ok
            ? "used"
            : hasSession
              ? "failed"
              : "installed-needs-linkedin-session",
          role: toolRole(tool.id),
          extracted: {
            emails: linkedinScraperResult?.emails?.length || 0,
            phones: linkedinScraperResult?.phones?.length || 0
          },
          error: linkedinScraperResult?.ok ? "" : linkedinScraperResult?.error || "",
          note: hasSession
            ? "Session-capable LinkedIn scraper was invoked through tools/hermes_linkedin_scraper_profile.py."
            : "Set HERMES_LINKEDIN_SESSION_JSON or HERMES_LINKEDIN_LI_AT to enable authenticated profile scraping."
        };
      }

      if (tool.id === "linkedin_profile_scraper_api") {
        const hasCookie = Boolean(process.env.HERMES_LINKEDIN_LI_AT);
        return {
          tool: tool.name,
          status: profileApiResult?.ok
            ? "used"
            : hasCookie
              ? "failed"
              : "installed-needs-li-at-cookie",
          role: toolRole(tool.id),
          extracted: {
            emails: profileApiResult?.emails?.length || 0,
            phones: profileApiResult?.phones?.length || 0
          },
          error: profileApiResult?.ok ? "" : profileApiResult?.error || "",
          note: hasCookie
            ? "Cookie-capable profile scraper was invoked through tools/hermes_linkedin_profile_api.mjs."
            : "Set HERMES_LINKEDIN_LI_AT to enable this scraper's cookie-based LinkedIn profile mode."
        };
      }

      if (tool.id === "jobspy") {
        return {
          tool: tool.name,
          status: jobSpyResult?.ok ? "used" : jobSpyResult?.skipped ? "skipped" : "failed",
          role: toolRole(tool.id),
          extracted: { jobs: jobSpyResult?.jobs?.length || 0 },
          error: jobSpyResult?.ok ? "" : jobSpyResult?.error || "",
          note: "Runs a small LinkedIn jobs/company search for business corroboration when owner profile evidence is being processed."
        };
      }

      if (tool.id === "py_linkedin_jobs_scraper") {
        return {
          tool: tool.name,
          status: pyLinkedinJobsResult?.ok ? "used" : pyLinkedinJobsResult?.skipped ? "skipped" : "failed",
          role: toolRole(tool.id),
          extracted: { jobs: pyLinkedinJobsResult?.jobs?.length || 0 },
          error: pyLinkedinJobsResult?.ok ? "" : pyLinkedinJobsResult?.error || "",
          note: pyLinkedinJobsResult?.skipped
            ? "Set HERMES_ENABLE_PY_LINKEDIN_JOBS=1 to run this browser-based LinkedIn jobs corroboration tool."
            : "Runs browser-based LinkedIn jobs/company corroboration."
        };
      }

      return {
        tool: tool.name,
        status: "installed-support-tool",
        role: toolRole(tool.id),
        extracted: {},
        note: "This tool supports LinkedIn/job/company corroboration; owner contact extraction continues through public profile/article evidence."
      };
    });
  }
}

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: "linkedin-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 line = stdout.trim().split(/\r?\n/).reverse().find((entry) => entry.trim().startsWith("{"));
        resolve(JSON.parse(line || stdout));
      } catch {
        resolve({ ok: false, error: stderr || stdout || "linkedin profile API bridge failed" });
      }
    });
    child.on("error", (error) => {
      clearTimeout(timer);
      resolve({ ok: false, error: error.message });
    });
    child.stdin.end(JSON.stringify(payload));
  });
}

function mergeArrays(a, b) {
  return [...new Set([...(Array.isArray(a) ? a : []), ...(Array.isArray(b) ? b : [])].filter(Boolean))];
}

function looksLikePath(command) {
  return String(command || "").includes("/") || String(command || "").includes("\\");
}

function toolRole(id) {
  return ({
    linkedin_scraper: "LinkedIn personal profile scraper",
    jobspy: "LinkedIn job/company corroboration scraper",
    linkedin_profile_scraper_api: "LinkedIn cookie-based profile scraper",
    py_linkedin_jobs_scraper: "LinkedIn jobs/company corroboration scraper",
    scrapling: "Public profile/article extractor"
  })[id] || "profile support tool";
}
