"""
ats_check.py — Cluster + direct-phrase ATS scorer for tailored resumes.

Replaces the legacy `scripts/linkedin_ats_simulation.py` for the day-to-day
cover-letter / resume pipeline. Reads a generated DOCX, scores it against a
JD's keyword cluster, and reports direct-phrase matches.

Usage:
    python scripts/ats_check.py --resume <path.docx>
    python scripts/ats_check.py --resume <path.docx> --jd <path.txt>

If --jd is omitted, the script prompts for the JD text on stdin. The cluster
list below is the default for Rob's MarOps / RevOps search; you can override
clusters by editing this script or by passing --clusters via a JSON file.

Decision band:
    <55  → stop and discuss major gaps
    55-74 → note gaps, proceed (one revision typically clears MED clusters)
    >=75 → strong

History:
    2026-07-23  carved out from /root/.hermes/staging/job1_2025-07-23_pantheon/
                ats_check.py after the Pantheon tailoring session produced a
                74.0% cluster / 88% phrase score on the Sr. Manager RevOps JD.
"""

import argparse
import json
import re
import sys

from docx import Document


DEFAULT_CLUSTERS = {
    "Funnel/Revenue strategy": [
        "funnel", "pipeline", "revenue", "bdr", "sdr", "territory",
        "quota", "coverage", "forecasting", "top-of-funnel", "yield",
    ],
    "CRM/MAP/Stack": [
        "marketo", "salesforce", "hubspot", "demandbase", "zoominfo",
        "clay", "workfront", "monday.com",
    ],
    "BI/Reporting/Attribution": [
        "tableau", "looker", "power", "bi", "snowflake", "adobe",
        "analytics", "ga4", "google", "attribution", "sql",
    ],
    "AI/Automation": [
        "claude", "code", "n8n", "zapier", "mcp", "ai", "prompt",
        "llm", "agents", "workflows",
    ],
    "Conversion/Auditing": [
        "optimization", "a/b", "multivariate", "landing", "conversion",
        "semrush", "ahrefs", "screaming", "brightedge",
    ],
    "Cross-functional": [
        "leadership", "stakeholder", "cross-functional", "collaboration",
        "remote", "slackbased", "slack",
    ],
    "Seniority/Experience": [
        "senior", "partner", "manager", "director", "analyst",
        "marketing", "operations", "b2b", "saas", "10", "years",
    ],
    "Data narrative": [
        "dashboard", "reporting", "diagnose", "recommend", "data",
        "business", "narratives", "decisions",
    ],
}

DEFAULT_PHRASES = [
    "strategic thought partner", "analytical counterpart", "channel mix",
    "BDR capacity", "territory decisions", "pipeline coverage",
    "funnel leakage", "root-cause analysis", "structural fix",
    "top-of-funnel yield", "RevOps peers", "thinking partner",
    "push back on leadership", "B2B SaaS",
    "Marketo", "Salesforce", "HubSpot", "Tableau", "Snowflake", "Workfront",
    "Claude Code", "n8n", "Zapier", "MCP connectors",
]


def tokenize(text):
    text = re.sub(r"[^a-z0-9+/.\s-]", " ", text.lower())
    return {t for t in re.split(r"\s+", text) if len(t) > 2}


def read_docx(path):
    doc = Document(path)
    return "\n".join(p.text for p in doc.paragraphs)


def cluster_score(resume_tokens, keywords):
    hits = sum(1 for k in keywords if k in resume_tokens)
    return hits, len(keywords), round(100 * hits / len(keywords), 1)


def main():
    parser = argparse.ArgumentParser(description="ATS cluster + phrase scorer.")
    parser.add_argument("--resume", required=True, help="Path to tailored .docx")
    parser.add_argument("--jd", help="Path to JD text file (optional)")
    parser.add_argument("--clusters", help="Path to clusters JSON override")
    parser.add_argument("--phrases", help="Path to phrases JSON list override")
    args = parser.parse_args()

    resume = read_docx(args.resume)
    resume_tokens = tokenize(resume)

    clusters = DEFAULT_CLUSTERS
    if args.clusters:
        with open(args.clusters) as f:
            clusters = json.load(f)

    phrases = DEFAULT_PHRASES
    if args.phrases:
        with open(args.phrases) as f:
            phrases = json.load(f)

    print(f"Resume: {args.resume}")
    print(f"Resume tokens: {len(resume_tokens)}")
    print()

    print("Cluster coverage:")
    print("-" * 60)
    total = 0
    total_possible = 0
    for name, kws in clusters.items():
        hits, total_kws, pct = cluster_score(resume_tokens, kws)
        print(f"  {name:30s} {hits:2d}/{total_kws:2d}  {pct:5.1f}%")
        total += hits
        total_possible += total_kws
    print("-" * 60)
    overall_pct = round(100 * total / total_possible, 1)
    print(f"  OVERALL                       {total:3d}/{total_possible:3d}  {overall_pct:5.1f}%")
    print()

    print("Direct phrase hits:")
    print("-" * 60)
    phrase_hits = 0
    for ph in phrases:
        in_resume = ph.lower() in resume.lower()
        if in_resume:
            phrase_hits += 1
            print(f"  ✓ {ph}")
        else:
            print(f"  ✗ {ph}")
    print("-" * 60)
    phrase_pct = round(100 * phrase_hits / len(phrases), 1)
    print(f"  DIRECT PHRASE MATCH: {phrase_hits}/{len(phrases)}  {phrase_pct}%")
    print()

    if overall_pct < 55:
        print("DECISION: BLOCK — score <55, stop and discuss major gaps.")
        sys.exit(2)
    elif overall_pct < 75:
        print("DECISION: PROCEED — note MED cluster gaps, one revision round typical.")
    else:
        print("DECISION: STRONG — score >=75, ship.")


if __name__ == "__main__":
    main()
