"""
Stage 8 — Offer Agent  (Engagement role)
==========================================
WHAT THIS DOES
  Once a HUMAN has approved the offer terms, this agent:
    1. delivers the offer letter with an e-signature link and a clear validity window,
    2. sends reminders as the deadline approaches (and an expiring-soon note near the end),
    3. answers ROUTINE FAQs (start date, benefits, remote policy, equipment) instantly, and
    4. tracks the response:  ACCEPTED -> congratulations + advance to Hired/onboarding,
                             DECLINED -> warm thank-you + capture feedback + notify recruiter.

THE TWO HARD GATES
  GATE 1 — No human-approved terms, no offer. If offer_approved isn't TRUE with a named approver,
           the agent escalates instead of sending. It never composes or sends terms on its own.
  GATE 2 — The agent NEVER negotiates. Any question touching compensation, equity, title, or
           terms is routed to the recruiter immediately. FAQs are routine logistics only.

SAFETY (same posture as Stages 2-7)
  - DRY_RUN = True by default: prints what it WOULD do, sends nothing.
  - Texts only to candidates who consented.
  - Every action written to an audit log.
"""

import csv, json, sys
from datetime import datetime, timezone

# =====================================================================
#  CONFIG  —  EDIT THIS SECTION
# =====================================================================
DRY_RUN = True
CANDIDATES_CSV = "stage8_candidates_sample.csv"
OFFERS_JSON    = "stage8_offers_sample.json"
AUDIT_LOG      = "stage8_offer_audit_log.csv"

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

OFFER_DELIVERY = ("Hi {first_name}, we're thrilled to extend you an offer for {job_title} at {company}! "
                  "Your full offer letter and benefits packet are ready to review and sign here: {esign_link} "
                  "The offer is valid through {valid_until} ({validity_days} days). "
                  "Questions? Just reply — we're happy to help. Congratulations!")
OFFER_REMINDER = ("Hi {first_name}, a friendly reminder that your {job_title} offer is waiting for you: {esign_link} "
                  "It's valid through {valid_until}. If you're weighing things up, that's completely normal — "
                  "reply with any questions.")
EXPIRING_SOON  = ("Hi {first_name}, a heads-up: your {job_title} offer expires {valid_until}. "
                  "If you need more time or want to talk anything through, reply and we'll connect you "
                  "with {recruiter} right away — we'd hate to lose you to a deadline.")
ACCEPT_CONGRATS= ("Hi {first_name}, CONGRATULATIONS and welcome to {company}! 🎉 We're genuinely excited. "
                  "Next up: onboarding — you'll receive your welcome packet and pre-start checklist shortly.")
DECLINE_THANKS = ("Hi {first_name}, thank you for letting us know, and for the time you invested with us. "
                  "We're sorry it didn't work out this time and we'd welcome you back in the future. "
                  "If you're open to it, one quick question: what was the biggest factor in your decision? "
                  "It helps us improve. Either way — best of luck, sincerely.")

# =====================================================================
#  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 recruiter in notify_person().")

def generate_esign_link(cand):
    """Offer letter e-signature (DocuSign / Dropbox Sign / PandaDoc...)."""
    return f"https://esign.acme.example/offer/{cand['candidate_id'].lower()}"

def advance_stage(cand, to_stage):
    if DRY_RUN: print(f"      [DRY-RUN ADVANCE] {cand['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
# =====================================================================
ESCALATE_KEYWORDS = ("salary", "compensation", "comp", "equity", "stock", "bonus", "negotiat",
                     "title", "level", "terms", "counter")

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 reach_candidate(cand, body, subject):
    send_email(cand["email"], subject, 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 handle_question(cand, q, faq, rname, remail, audit):
    """Routine FAQ -> instant answer. Anything about terms -> human, immediately."""
    ql = q.strip().lower()
    if any(k in ql for k in ESCALATE_KEYWORDS):
        print(f"   Question touches TERMS ('{q}') — escalating to {rname}. The agent does not negotiate.")
        notify_person(rname, remail, f"Negotiation question — {cand['first_name']} {cand['last_name']}",
                      f"The candidate asked about: '{q}'. Please take this conversation directly — "
                      "the agent has not replied on terms.")
        audit.append([cand["candidate_id"], "negotiation_escalated", q]); return
    key = ql.replace(" ", "_")
    if key in faq:
        print(f"   Routine FAQ ('{q}') — answering instantly.")
        reach_candidate(cand, f"Hi {cand['first_name']}, great question! {faq[key]}",
                        subject=f"Re: your question about your {cand['job_title']} offer")
        audit.append([cand["candidate_id"], "faq_answered", q])
    else:
        print(f"   Unrecognized question ('{q}') — routing to {rname} rather than guessing.")
        notify_person(rname, remail, f"Candidate question — {cand['first_name']} {cand['last_name']}",
                      f"The candidate asked: '{q}'. Not in the routine FAQ set — please reply personally.")
        audit.append([cand["candidate_id"], "question_routed", q])

def process(cand, data, audit):
    cfg = data["config"]; faq = data.get("faq", {}); recruiters = data.get("recruiters", {})
    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']}")

    st = cand.get("offer_status", "").strip().lower()
    days = to_int(cand.get("days_since_sent"))
    validity = cfg["offer_validity_days"]
    esign = generate_esign_link(cand)
    valid_until = f"day {validity}"  # in production: a real date from the send timestamp

    # ---- 1) To send -> GATE 1: only with human-approved terms ----
    if st == "to_send":
        if not (is_true(cand.get("offer_approved")) and cand.get("approver", "").strip()):
            print("   BLOCKED — offer terms not human-approved (or no approver named). Escalating, not sending.")
            notify_person(rname, remail, f"Offer awaiting approval — {name}",
                          "This offer is queued but terms aren't approved yet. Please approve (with your name "
                          "recorded) so the agent can deliver it. The agent will not send unapproved terms.")
            audit.append([cand["candidate_id"], "blocked_unapproved", "no approval"]); return
        print(f"   Terms approved by {cand['approver']} ✓ — delivering offer + e-signature (valid {validity} days).")
        reach_candidate(cand, fill(OFFER_DELIVERY, first_name=cand["first_name"], job_title=cand["job_title"],
                                   company=COMPANY_NAME, esign_link=esign, valid_until=valid_until,
                                   validity_days=validity),
                        subject=f"Your offer from {COMPANY_NAME} — {cand['job_title']} 🎉")
        audit.append([cand["candidate_id"], "offer_sent", f"approved by {cand['approver']}"]); return

    # ---- 2) Sent -> answer questions / remind / expiring-soon ----
    if st == "sent":
        q = cand.get("candidate_question", "").strip()
        if q:
            handle_question(cand, q, faq, rname, remail, audit); return
        remaining = validity - days
        if remaining <= cfg["expiring_soon_days"]:
            print(f"   Offer expiring soon ({remaining} day(s) left). Urgent-but-warm note + flag to recruiter.")
            reach_candidate(cand, fill(EXPIRING_SOON, first_name=cand["first_name"], job_title=cand["job_title"],
                                       valid_until=valid_until, recruiter=rname),
                            subject=f"Your {cand['job_title']} offer expires soon")
            notify_person(rname, remail, f"Offer expiring — {name}",
                          f"{name}'s offer expires in {remaining} day(s) with no response. Consider a personal call.")
            audit.append([cand["candidate_id"], "expiring_warned", f"{remaining}d left"])
        elif days >= cfg["remind_after_days"]:
            print(f"   No response in {days}d — sending a friendly reminder.")
            reach_candidate(cand, fill(OFFER_REMINDER, first_name=cand["first_name"], job_title=cand["job_title"],
                                       esign_link=esign, valid_until=valid_until),
                            subject=f"Reminder: your {COMPANY_NAME} offer is waiting")
            audit.append([cand["candidate_id"], "reminded", f"{days}d"])
        else:
            print(f"   Sent {days}d ago — within the quiet window. No action.")
            audit.append([cand["candidate_id"], "waiting", f"{days}d"])
        return

    # ---- 3) Accepted -> congratulate + advance to onboarding ----
    if st == "accepted":
        print("   ACCEPTED ✓ — congratulations sent, advancing to Hired / onboarding.")
        reach_candidate(cand, fill(ACCEPT_CONGRATS, first_name=cand["first_name"], company=COMPANY_NAME),
                        subject=f"Welcome to {COMPANY_NAME}! 🎉")
        advance_stage(cand, "Hired")
        notify_person(rname, remail, f"Offer accepted — {name}", "Signed and accepted. Onboarding agent takes it from here.")
        audit.append([cand["candidate_id"], "accepted", "advanced to Hired"]); return

    # ---- 4) Declined -> warm thanks + capture feedback + notify recruiter ----
    if st == "declined":
        print("   DECLINED — warm thank-you + feedback ask; notifying recruiter (close-out is human).")
        reach_candidate(cand, fill(DECLINE_THANKS, first_name=cand["first_name"]),
                        subject=f"Thank you, {cand['first_name']}")
        notify_person(rname, remail, f"Offer declined — {name}",
                      "The candidate declined. Feedback request sent. Please close out the req status and "
                      "decide whether to extend to a backup candidate.")
        audit.append([cand["candidate_id"], "declined", "feedback requested"]); 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(OFFERS_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 8 Offer Agent — {'DRY-RUN (nothing sent)' if DRY_RUN else 'LIVE'}")
    audit = []
    for c in cands:
        if c.get("pipeline_stage", "").strip().lower() != "offer" or c.get("status", "").strip().lower() != "active":
            print(f"\n• {c.get('first_name','?')} — skipped (not an active offer-stage candidate)"); continue
        process(c, data, 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()
