"""
Stage 6 — Decision-Stage Agent  (Engagement role)
===================================================
WHAT THIS DOES
  Around the human hire/no-hire decision, this agent:
    1. consolidates the panel's scorecards into ONE decision packet for the deciders,
    2. keeps the candidate warm with an honest timeline so they don't go cold,
    3. nudges the deciders as the deadline approaches and passes, and
    4. acts on the recorded decision:
         - hire    -> advances to reference/background and sends the candidate good news,
         - no_hire -> routes to the recruiter to deliver a PERSONAL rejection (never auto-sent).
  It never makes the hire/no-hire call, and it never auto-rejects.

SAFETY (same posture as Stages 2-5)
  - DRY_RUN = True by default: prints what it WOULD do, sends nothing.
  - Texts only to candidates who consented.
  - The decision is always human; rejections are always delivered by a person.
  - Every action written to an audit log.

You only edit the CONFIG section.
"""

import csv, json, sys
from datetime import datetime, timedelta, timezone
try:
    from zoneinfo import ZoneInfo
except ImportError:
    sys.exit("This script needs Python 3.9+ (zoneinfo).")

# =====================================================================
#  CONFIG  —  EDIT THIS SECTION
# =====================================================================
DRY_RUN = True
CANDIDATES_CSV = "stage6_candidates_sample.csv"
DECISION_JSON  = "stage6_decision_sample.json"
AUDIT_LOG      = "stage6_decision_audit_log.csv"

COMPANY_NAME = "Acme Talent"
FROM_NAME    = "Arun from Acme Recruiting"

# ---- Templates ({placeholders} fill per candidate). Edit freely. ----
WARM_TIMELINE = ("Hi {first_name}, a quick update on the {job_title} role: you're in our final review. "
                 "We expect to have an update for you by {expected_by}. Thanks for your patience — "
                 "we're glad you're in the running.")
WARM_DELAY    = ("Hi {first_name}, an honest update on the {job_title} role: our review is taking a little "
                 "longer than planned. We now expect to be back to you by {expected_by}. Thank you for bearing with us.")
WARM_GOODNEWS = ("Hi {first_name}, great news — the team would like to move forward with you for {job_title}! "
                 "Next we'll run reference and background checks, then talk through an offer. More soon.")
# A starting draft the recruiter can personalize before sending — the agent never sends this itself.
REJECTION_DRAFT = ("Hi {first_name}, thank you for the time you invested interviewing for {job_title}. "
                   "After careful consideration we've decided not to move forward. This was a hard call against a "
                   "strong field, and we'd genuinely welcome you to apply again. Wishing you the best.")

# =====================================================================
#  PROVIDER HOOKS  —  plug your services in here (examples commented)
# =====================================================================
def send_email(to, subject, body):
    if DRY_RUN: print(f"      [DRY-RUN EMAIL] to {to}  |  {subject}"); return True
    raise NotImplementedError("Add email provider in send_email().")

def send_sms(to, body):
    if DRY_RUN: print(f"      [DRY-RUN TEXT]  to {to}"); return True
    raise NotImplementedError("Add SMS provider in send_sms().")

def notify_person(name, email, subject, body):
    if DRY_RUN: print(f"      [DRY-RUN → {name}]  |  {subject}"); return True
    raise NotImplementedError("Route to decider/recruiter in notify_person().")

def advance_stage(candidate, to_stage):
    if DRY_RUN: print(f"      [DRY-RUN ADVANCE] {candidate['candidate_id']} -> {to_stage}"); return True
    raise NotImplementedError("Update the candidate's stage in your ATS in advance_stage().")

# =====================================================================
#  ENGINE  —  you can leave this alone
# =====================================================================
def is_true(v): return str(v).strip().lower() in ("true", "yes", "y", "1")
def to_int(v):
    try: return int(v or 0)
    except ValueError: return 0
def fill(t, **kw): return t.format(**kw)
def date_str(dt, tzname): return dt.astimezone(ZoneInfo(tzname)).strftime("%a %b %d")

def warm_candidate(cand, template, expected_by):
    body = fill(template, first_name=cand["first_name"], job_title=cand["job_title"], expected_by=expected_by)
    send_email(cand["email"], f"Update on your {cand['job_title']} application", body)
    if cand["phone"] and is_true(cand["consent_to_contact"]):
        send_sms(cand["phone"], body)
    elif cand["phone"]:
        print("      (candidate text skipped — no consent)")

def process(cand, data, now, audit):
    cfg = data["config"]; roster = data["deciders_roster"]; recruiters = data.get("recruiters", {})
    reqcfg = data["reqs"].get(cand["job_req_id"])
    name = f'{cand["first_name"]} {cand["last_name"]}'.strip()
    rname = cand["recruiter_owner"]; remail = recruiters.get(rname, "")
    print(f"\n• {name}  [{cand['candidate_id']}]  →  {cand['job_title']}")
    if not reqcfg:
        print("   SKIPPED — no decider config for this req."); audit.append([cand["candidate_id"], "skipped", "no config"]); return
    deciders = reqcfg["deciders"]
    deadline_days = cfg["decision_deadline_days"]; warm_every = cfg["warm_update_every_days"]
    st = cand.get("decision_status", "").strip().lower()
    decision = cand.get("decision", "").strip().lower()

    # ---- Decided: act on the human's call ----
    if st == "decided":
        if decision == "hire":
            print("   Decision: HIRE. Advancing to reference/background and sending good news.")
            advance_stage(cand, "Reference / Background")
            warm_candidate(cand, WARM_GOODNEWS, expected_by="")
            audit.append([cand["candidate_id"], "advanced_hire", "to reference/background"])
        elif decision == "no_hire":
            print("   Decision: NO-HIRE. Routing to recruiter for a PERSONAL rejection (agent will not auto-send).")
            notify_person(rname, remail, f"Please deliver rejection — {name} ({cand['job_title']})",
                          "The team decided not to proceed. Please send a personal note promptly. "
                          "Suggested draft (review & edit before sending):\n\n" +
                          fill(REJECTION_DRAFT, first_name=cand["first_name"], job_title=cand["job_title"]))
            audit.append([cand["candidate_id"], "rejection_to_human", "draft provided"])
        else:
            print(f"   Marked decided but decision is '{decision}' — escalating to recruiter to clarify.")
            notify_person(rname, remail, f"Unclear decision — {name}", "Status is 'decided' but no hire/no_hire recorded.")
            audit.append([cand["candidate_id"], "escalated", "unclear decision"])
        return

    # ---- Packet pending: consolidate feedback, send to deciders, start the clock ----
    if st == "packet_pending":
        expected = date_str(now + timedelta(days=deadline_days), cand["timezone"])
        print(f"   Consolidating scorecards into a decision packet. Deciders: {', '.join(roster[d]['name'] for d in deciders)}.")
        for d in deciders:
            notify_person(roster[d]["name"], roster[d]["email"],
                          f"Decision needed — {name} ({cand['job_title']}) by {expected}",
                          "Consolidated panel scorecards attached. Please record your hire / no-hire call by the deadline. "
                          "(The agent packages feedback only — it does not score or recommend.)")
        print("   Keeping the candidate warm with an honest timeline.")
        warm_candidate(cand, WARM_TIMELINE, expected_by=expected)
        audit.append([cand["candidate_id"], "packet_sent", f"due {expected}"])
        return

    # ---- Awaiting decision: nudge deciders + keep candidate warm ----
    if st == "awaiting_decision":
        days = to_int(cand.get("days_since_packet"))
        remaining = deadline_days - days
        expected = date_str(now + timedelta(days=max(remaining, 1)), cand["timezone"])
        since_update = to_int(cand.get("days_since_last_update"))

        if remaining <= 0:
            print(f"   Decision OVERDUE by {-remaining} day(s). Nudging deciders + honest delay note to candidate.")
            for d in deciders:
                notify_person(roster[d]["name"], roster[d]["email"], f"Past deadline — decision on {name}",
                              f"The hire decision for {name} ({cand['job_title']}) is past the {deadline_days}-day deadline. "
                              "Please make the call so we don't lose the candidate.")
            warm_candidate(cand, WARM_DELAY, expected_by=expected)
            audit.append([cand["candidate_id"], "deciders_nudged", "overdue"])
        else:
            if remaining <= 1:
                print(f"   Deadline approaching ({remaining} day left). Gentle nudge to deciders.")
                for d in deciders:
                    notify_person(roster[d]["name"], roster[d]["email"], f"Decision due soon — {name}",
                                  f"A friendly nudge: the decision on {name} is due by {expected}.")
            if since_update >= warm_every:
                print(f"   Candidate update due ({since_update}d since last). Keeping them warm.")
                warm_candidate(cand, WARM_TIMELINE, expected_by=expected)
                audit.append([cand["candidate_id"], "warm_update", f"{remaining}d left"])
            else:
                print(f"   On track ({remaining}d left); candidate updated recently. No message needed.")
                audit.append([cand["candidate_id"], "on_track", f"{remaining}d left"])
        return

    print(f"   Status '{st}' — no action."); audit.append([cand["candidate_id"], "noop", st])

def main():
    now = datetime.now(timezone.utc)
    try:
        with open(CANDIDATES_CSV, newline="", encoding="utf-8") as f: cands = list(csv.DictReader(f))
        with open(DECISION_JSON, encoding="utf-8") as f: data = json.load(f)
    except FileNotFoundError as e: sys.exit(f"Missing input file: {e.filename}")

    print(f"Stage 6 Decision-Stage Agent — {'DRY-RUN (nothing sent)' if DRY_RUN else 'LIVE'}")
    audit = []
    for c in cands:
        if c.get("pipeline_stage", "").strip().lower() != "final interview" or c.get("status", "").strip().lower() != "active":
            print(f"\n• {c.get('first_name','?')} — skipped (not an active final-interview candidate)"); continue
        process(c, data, now, audit)

    stamp = now.isoformat(timespec="seconds"); new = True
    try: open(AUDIT_LOG, "x").close()
    except FileExistsError: new = False
    with open(AUDIT_LOG, "a", newline="", encoding="utf-8") as f:
        w = csv.writer(f)
        if new: w.writerow(["run_at_utc", "candidate_id", "action", "detail"])
        for r in audit: w.writerow([stamp] + r)
    print(f"\nDone. Processed {len(audit)} candidate(s). Log: {AUDIT_LOG}")

if __name__ == "__main__":
    main()
