#!/usr/bin/env python3
import json
import os
import re
import subprocess
import sys
from pathlib import Path
from urllib.parse import urlparse


EMAIL_RE = re.compile(r"[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}", re.I)
PHONE_RE = re.compile(r"(?:\+?\d[\d\s().-]{7,}\d)")
URL_RE = re.compile(r"https?://[^\s\"'<>]+", re.I)
PROFILE_HOST_RE = re.compile(r"(linkedin\.com|facebook\.com|instagram\.com|twitter\.com|x\.com|tiktok\.com|about\.me|linktr\.ee|beacons\.ai)", re.I)


def main():
    payload = json.loads(sys.stdin.read() or "{}")
    vendor_dir = Path(payload.get("vendorDir") or "tools/vendor")
    mode = payload.get("mode") or "website"
    timeout = int(payload.get("timeout") or 30)
    if mode == "website":
        result = website_mode(payload, vendor_dir, timeout)
    elif mode == "profiles":
        result = profiles_mode(payload, vendor_dir, timeout)
    else:
        result = {"ok": False, "error": f"unknown mode: {mode}"}
    print(json.dumps(result))


def website_mode(payload, vendor_dir, timeout):
    url = payload.get("url") or ""
    domain = payload.get("domain") or host_from_url(url)
    disabled_tools = set(payload.get("disabledTools") or [])
    tool_policy = payload.get("toolPolicy") or {}
    attempts = []
    links = []
    emails = set(payload.get("seedEmails") or [])
    phones = set(payload.get("seedPhones") or [])
    evidence = []

    photon = vendor_dir / "Photon" / "photon.py"
    if "s0md3v/Photon" in disabled_tools:
        attempts.append(policy_skipped("s0md3v/Photon", tool_policy))
    elif url and photon.exists():
        out = run_tool([sys.executable, str(photon), "-u", url, "-l", "1", "--only-urls"], timeout)
        attempts.append(attempt("s0md3v/Photon", out))
        parsed_links = parse_urls(out.get("stdout", ""))
        links.extend(parsed_links)
        evidence.append({
            "tool": "s0md3v/Photon",
            "source": "website_enrichment",
            "url": url,
            "links": parsed_links[:40],
            "textSample": sample(out),
        })
    else:
        attempts.append(skipped("s0md3v/Photon", "not installed"))

    harvester = vendor_dir / "theHarvester"
    harvester_bin = Path(sys.executable).parent / "theHarvester"
    if "laramies/theHarvester" in disabled_tools:
        attempts.append(policy_skipped("laramies/theHarvester", tool_policy))
    elif domain and harvester_bin.exists():
        out = run_tool([str(harvester_bin), "-d", domain, "-b", "duckduckgo,crtsh", "-l", "50", "-q"], timeout)
        attempts.append(attempt("laramies/theHarvester", out))
        text = f"{out.get('stdout', '')}\n{out.get('stderr', '')}"
        emails.update(EMAIL_RE.findall(text))
        phones.update(PHONE_RE.findall(text))
        parsed_links = parse_urls(text)
        links.extend(parsed_links)
        evidence.append({
            "tool": "laramies/theHarvester",
            "source": "domain_enrichment",
            "url": domain,
            "emails": sorted(emails)[:20],
            "links": parsed_links[:40],
            "textSample": sample(out),
        })
    elif domain and (harvester / "theHarvester" / "theHarvester.py").exists():
        out = run_tool([sys.executable, str(harvester / "theHarvester" / "theHarvester.py"), "-d", domain, "-b", "duckduckgo,crtsh", "-l", "50", "-q"], timeout)
        attempts.append(attempt("laramies/theHarvester", out))
        text = f"{out.get('stdout', '')}\n{out.get('stderr', '')}"
        emails.update(EMAIL_RE.findall(text))
        phones.update(PHONE_RE.findall(text))
        parsed_links = parse_urls(text)
        links.extend(parsed_links)
        evidence.append({
            "tool": "laramies/theHarvester",
            "source": "domain_enrichment",
            "url": domain,
            "emails": sorted(emails)[:20],
            "links": parsed_links[:40],
            "textSample": sample(out),
        })
    else:
        attempts.append(skipped("laramies/theHarvester", "not installed"))

    cleaned_links = unique(clean_url(link) for link in links if clean_url(link))
    return {
        "ok": any(a.get("status") == "used" for a in attempts),
        "mode": "website",
        "links": cleaned_links[:120],
        "profileLinks": [link for link in cleaned_links if PROFILE_HOST_RE.search(link)][:40],
        "emails": sorted(filter_email(email) for email in emails if filter_email(email))[:30],
        "phones": sorted(filter_phone(phone) for phone in phones if filter_phone(phone))[:30],
        "evidence": evidence,
        "toolAttempts": attempts,
        "toolPolicy": tool_policy,
    }


def profiles_mode(payload, vendor_dir, timeout):
    usernames = payload.get("usernames") or []
    disabled_tools = set(payload.get("disabledTools") or [])
    tool_policy = payload.get("toolPolicy") or {}
    attempts = []
    profile_links = []
    evidence = []
    per_tool_timeout = max(5, min(18, timeout // max(1, len(usernames))))

    for username in usernames[:8]:
        maigret = vendor_dir / "maigret"
        if "soxoj/maigret" in disabled_tools:
            attempts.append(policy_skipped("soxoj/maigret", tool_policy, username=username))
        elif maigret.exists():
            out = run_tool([sys.executable, "-m", "maigret", username, "--no-color", "--timeout", "5", "--top-sites", "500"], per_tool_timeout)
            attempts.append(attempt("soxoj/maigret", out, username=username))
            links = profile_urls_from_output(out)
            profile_links.extend(links)
            evidence.append(profile_evidence("soxoj/maigret", username, links, out))
        else:
            attempts.append(skipped("soxoj/maigret", "not installed", username=username))

        sherlock = vendor_dir / "sherlock"
        sherlock_bin = Path(sys.executable).parent / "sherlock"
        if "sherlock-project/sherlock" in disabled_tools:
            attempts.append(policy_skipped("sherlock-project/sherlock", tool_policy, username=username))
        elif sherlock_bin.exists():
            out = run_tool([str(sherlock_bin), username, "--print-found", "--timeout", "5", "--no-color"], per_tool_timeout)
            attempts.append(attempt("sherlock-project/sherlock", out, username=username))
            links = profile_urls_from_output(out)
            profile_links.extend(links)
            evidence.append(profile_evidence("sherlock-project/sherlock", username, links, out))
        elif sherlock.exists():
            out = run_tool([sys.executable, "-m", "sherlock_project", username, "--print-found", "--timeout", "5", "--no-color"], per_tool_timeout)
            attempts.append(attempt("sherlock-project/sherlock", out, username=username))
            links = profile_urls_from_output(out)
            profile_links.extend(links)
            evidence.append(profile_evidence("sherlock-project/sherlock", username, links, out))
        else:
            attempts.append(skipped("sherlock-project/sherlock", "not installed", username=username))

    social_analyzer = vendor_dir / "social-analyzer"
    if social_analyzer.exists():
        attempts.append(skipped("qeeqbox/social-analyzer", "installed but no stable local CLI bridge configured"))
    else:
        attempts.append(skipped("qeeqbox/social-analyzer", "not installed"))

    links = unique(clean_url(link) for link in profile_links if clean_url(link))
    return {
        "ok": any(a.get("status") == "used" for a in attempts),
        "mode": "profiles",
        "usernames": usernames[:8],
        "profileLinks": links[:120],
        "links": links[:120],
        "emails": [],
        "phones": [],
        "evidence": evidence,
        "toolAttempts": attempts,
        "toolPolicy": tool_policy,
    }


def run_tool(args, timeout):
    try:
        proc = subprocess.run(
            args,
            text=True,
            capture_output=True,
            timeout=timeout,
            cwd=str(Path(args[1]).parent) if len(args) > 1 and args[1].endswith(".py") else None,
        )
        return {
            "ok": proc.returncode == 0,
            "returncode": proc.returncode,
            "stdout": proc.stdout[-20000:],
            "stderr": proc.stderr[-6000:],
        }
    except subprocess.TimeoutExpired as exc:
        return {
            "ok": False,
            "timeout": True,
            "stdout": (exc.stdout or "")[-4000:] if isinstance(exc.stdout, str) else "",
            "stderr": (exc.stderr or "")[-4000:] if isinstance(exc.stderr, str) else "Timed out",
        }
    except Exception as exc:
        return {"ok": False, "error": str(exc), "stdout": "", "stderr": ""}


def attempt(tool, output, username=None):
    return {
        "tool": tool,
        "username": username or "",
        "status": "used" if output.get("ok") else "failed",
        "returncode": output.get("returncode"),
        "timeout": bool(output.get("timeout")),
        "error": "" if output.get("ok") else output.get("error") or output.get("stderr", "")[:400],
    }


def skipped(tool, reason, username=None):
    return {
        "tool": tool,
        "username": username or "",
        "status": "missing" if reason == "not installed" else "skipped",
        "reason": reason,
    }


def policy_skipped(tool, policy, username=None):
    return skipped(tool, (policy.get("reasons") or {}).get(tool) or "disabled by owner-evidence timeout policy", username=username)


def profile_evidence(tool, username, links, output):
    return {
        "tool": tool,
        "source": "owner_profile_rabbit_hole",
        "username": username,
        "profileLinks": links[:40],
        "textSample": sample(output),
    }


def profile_urls_from_output(output):
    text = f"{output.get('stdout', '')}\n{output.get('stderr', '')}"
    return [url for url in parse_urls(text) if PROFILE_HOST_RE.search(url)]


def parse_urls(text):
    return [clean_url(match.group(0)) for match in URL_RE.finditer(text or "")]


def clean_url(url):
    value = str(url or "").strip().rstrip(".,);]")
    if not value.lower().startswith(("http://", "https://")):
        return ""
    try:
        parsed = urlparse(value)
        if not parsed.netloc:
            return ""
        return parsed._replace(fragment="").geturl().rstrip("/")
    except Exception:
        return ""


def filter_email(email):
    value = str(email or "").strip().lower()
    if not EMAIL_RE.fullmatch(value):
        return ""
    if value == "cmartorella@edge-security.com":
        return ""
    if "noreply" in value or value.endswith(("example.com", "test.com")):
        return ""
    return value


def filter_phone(phone):
    value = " ".join(str(phone or "").split())
    digits = re.sub(r"\D", "", value)
    if 7 <= len(digits) <= 15:
        return value
    return ""


def host_from_url(url):
    try:
        text = str(url or "")
        if not text.startswith(("http://", "https://")):
            text = "https://" + text
        return urlparse(text).netloc.replace("www.", "")
    except Exception:
        return ""


def unique(values):
    out = []
    seen = set()
    for value in values:
        if not value or value in seen:
            continue
        seen.add(value)
        out.append(value)
    return out


def sample(output):
    text = f"{output.get('stdout', '')}\n{output.get('stderr', '')}"
    return " ".join(text.split())[:900]


if __name__ == "__main__":
    main()
