"""Reply handling — classify replies and draft contextual responses.

Two responsibilities:
1. classify_reply() — 5-category reply classifier (autoresponder,
   hard opt-out, soft opt-out, bounce, engagement). Used by the
   pre-delivery safety check and the reply polling pipeline.
2. draft_response() — generate a contextual response to a human
   reply that the operator can review and send.

CLI: absel respond <lead_id> "<reply text>"
     absel respond <lead_id> --reply-file reply.txt
"""

import re
from datetime import datetime
from pathlib import Path
from typing import Optional

from state.backlog import get_lead, update_lead
from integrations.llm import call_llm

# ── Autoresponder patterns ──────────────────────────────────────────
# Machine-generated replies with no human intent. The sequence
# continues undisturbed. Checked BEFORE opt-out to catch vacation
# autoresponders that also contain opt-out language.

AUTORESPONDER_PATTERNS = [
    # Out of office variants
    "out of office",
    "out of the office",
    "on vacation",
    "on holiday",
    "on leave",
    "on pto",
    "currently away",
    "away from my desk",
    "away from the office",
    "i will be returning",
    "i will return on",
    "will be back on",
    "returning on",
    "back in the office on",
    "limited access to email",
    "intermittent access",
    "no access to email",
    # Auto-reply markers
    "this is an automated",
    "this is an auto-reply",
    "this is an automatic",
    "auto-reply",
    "autoreply",
    "automatic reply",
    "automated response",
    "auto response",
    "autoresponse",
    "do not reply to this",
    "this mailbox is not monitored",
    "no-reply",
    "noreply",
    # Acknowledgment templates
    "thank you for your email. i will",
    "thank you for your message. i will",
    "thanks for your email. i will",
    "i have received your email",
    "your email has been received",
    "your message has been received",
    # Redirect instructions (only in auto-context)
    "for immediate assistance",
    "for urgent matters",
    "in case of emergency contact",
    "please contact my colleague",
    "please reach out to",
]

# ── Bounce patterns ─────────────────────────────────────────────────
# Delivery failures — the address is bad. NOT an opt-out (the person
# didn't say they don't want email; the email just can't reach them).

BOUNCE_PATTERNS = [
    "mailer-daemon",
    "postmaster",
    "delivery status notification",
    "undeliverable",
    "undelivered",
    "message not delivered",
    "delivery failed",
    "delivery failure",
    "mailbox not found",
    "mailbox unavailable",
    "mailbox full",
    "user unknown",
    "no such user",
    "address rejected",
    "550 ",
    "553 ",
    "552 ",
]

# ── Hard opt-out phrases ────────────────────────────────────────────
# Explicit, unambiguous request to stop all contact. Permanent burn.

HARD_OPT_OUT_PHRASES = [
    "unsubscribe",
    "stop emailing",
    "stop contacting",
    "remove me",
    "take me off",
    "don't email me",
    "dont email me",
    "don't contact me",
    "dont contact me",
    "do not email",
    "do not contact",
    "opt out",
    "opt me out",
    "leave me alone",
    "please stop",
    "quit emailing",
    "remove from list",
    "remove from your list",
    "stop sending",
    "no more emails",
    "cease and desist",
]

# ── Soft opt-out phrases ────────────────────────────────────────────
# Polite decline that might be temporary. Pause, let operator decide.

SOFT_OPT_OUT_PHRASES = [
    "not interested",
    "no thanks",
    "no thank you",
    "not right now",
    "bad timing",
    "maybe later",
    "not looking",
    "we're all set",
    "we are all set",
    "already have a vendor",
    "already taken care of",
    "have a provider",
]


def classify_reply(reply_text: str) -> dict:
    """Classify a reply into one of 5 categories.

    Priority order:
    1. Check for bounce (delivery failure -- not a human reply)
    2. Check for autoresponder patterns
       - If autoresponder AND contains opt-out -> opt-out wins
       - If autoresponder only -> autoresponder
    3. Check for hard opt-out
    4. Check for soft opt-out
    5. Everything else -> engagement

    Returns dict:
    {
        "category": "autoresponder" | "hard_opt_out" | "soft_opt_out" |
                    "bounce" | "engagement",
        "matched_pattern": str or None,
        "confidence": "high" | "medium",
        "return_date": str or None,
    }
    """
    lower = reply_text.lower().strip()

    # --- 1. Bounces ---
    for pattern in BOUNCE_PATTERNS:
        if pattern in lower:
            return {
                "category": "bounce",
                "matched_pattern": pattern,
                "confidence": "high",
                "return_date": None,
            }

    # --- 2. Autoresponders ---
    autoresponder_match = None
    for pattern in AUTORESPONDER_PATTERNS:
        if pattern in lower:
            autoresponder_match = pattern
            break

    if autoresponder_match:
        for phrase in HARD_OPT_OUT_PHRASES:
            if phrase in lower:
                return {
                    "category": "hard_opt_out",
                    "matched_pattern": phrase,
                    "confidence": "high",
                    "return_date": None,
                }
        for phrase in SOFT_OPT_OUT_PHRASES:
            if phrase in lower:
                return {
                    "category": "soft_opt_out",
                    "matched_pattern": phrase,
                    "confidence": "high",
                    "return_date": None,
                }

        return {
            "category": "autoresponder",
            "matched_pattern": autoresponder_match,
            "confidence": "high",
            "return_date": _extract_return_date(lower),
        }

    # --- 3. Hard opt-out ---
    for phrase in HARD_OPT_OUT_PHRASES:
        if phrase in lower:
            return {
                "category": "hard_opt_out",
                "matched_pattern": phrase,
                "confidence": "high",
                "return_date": None,
            }

    # --- 4. Soft opt-out ---
    for phrase in SOFT_OPT_OUT_PHRASES:
        if phrase in lower:
            return {
                "category": "soft_opt_out",
                "matched_pattern": phrase,
                "confidence": "medium",
                "return_date": None,
            }

    # --- 5. Everything else is engagement ---
    return {
        "category": "engagement",
        "matched_pattern": None,
        "confidence": "medium",
        "return_date": None,
    }


def _extract_return_date(text: str) -> str | None:
    """Try to extract a return date from an autoresponder.

    Best-effort regex patterns for common date formats near
    "return", "back", "returning" keywords. Returns ISO date
    string if found, None otherwise. Never fails on unparseable
    dates.

    Patterns handled:
    - "returning on June 5" / "back on June 5th"
    - "return on 6/5" / "back on 6/5/2026"
    - "return 2026-06-05"
    - "back in the office on Monday" (too vague -> None)
    """
    months_map = {
        "january": 1, "february": 2, "march": 3, "april": 4,
        "may": 5, "june": 6, "july": 7, "august": 8,
        "september": 9, "october": 10, "november": 11, "december": 12,
    }
    now = datetime.utcnow()
    year = now.year

    # Pattern 1: "returning on June 5" or "back on June 5th"
    m = re.search(
        r'(?:return(?:ing)?|back)\s+(?:on\s+)?(?:the\s+)?'
        r'(january|february|march|april|may|june|july|august|'
        r'september|october|november|december)\s+(\d{1,2})(?:st|nd|rd|th)?',
        text, re.IGNORECASE
    )
    if m:
        month = months_map.get(m.group(1).lower())
        day = int(m.group(2))
        if month and 1 <= day <= 31:
            try:
                dt = datetime(year, month, day)
                if dt < now:
                    dt = datetime(year + 1, month, day)
                return dt.strftime("%Y-%m-%d")
            except ValueError:
                pass

    # Pattern 2: "return on 6/5" or "back on 6/5/2026"
    m = re.search(
        r'(?:return(?:ing)?|back)\s+(?:on\s+)?'
        r'(\d{1,2})/(\d{1,2})(?:/(\d{4}))?',
        text
    )
    if m:
        month = int(m.group(1))
        day = int(m.group(2))
        yr = int(m.group(3)) if m.group(3) else year
        if 1 <= month <= 12 and 1 <= day <= 31:
            try:
                dt = datetime(yr, month, day)
                if dt < now and not m.group(3):
                    dt = datetime(yr + 1, month, day)
                return dt.strftime("%Y-%m-%d")
            except ValueError:
                pass

    # Pattern 3: "return 2026-06-05"
    m = re.search(
        r'(?:return(?:ing)?|back)\s+(\d{4})-(\d{2})-(\d{2})',
        text
    )
    if m:
        try:
            dt = datetime(int(m.group(1)), int(m.group(2)), int(m.group(3)))
            return dt.strftime("%Y-%m-%d")
        except ValueError:
            pass

    return None

SERVICES_KNOWLEDGE_PATH = Path("./config/services-knowledge.yaml")


def _load_services_context() -> str:
    """Load compact services context for the response prompt."""
    import yaml

    if not SERVICES_KNOWLEDGE_PATH.exists():
        return ""

    try:
        with open(SERVICES_KNOWLEDGE_PATH) as f:
            knowledge = yaml.safe_load(f)
    except Exception:
        return ""

    company = knowledge.get("company", {})
    company_name = company.get("name", "ABS Xerox")

    lines = [
        f"You're with {company_name}.",
        "You sell copiers, printers, and multifunction devices. Physical machines.",
        "",
    ]

    never_say = knowledge.get("never_say", [])
    if never_say:
        lines.append("NEVER:")
        for rule in never_say:
            lines.append(f"- {rule}")
        lines.append("")

    return "\n".join(lines)


def _build_response_prompt(lead: dict, reply_text: str, batch_intel: str | None = None) -> str:
    """Build the response drafting prompt.

    Adjusts tone based on lead_type:
    - Cold outreach leads: standard introduction-to-conversation shift
    - Renewal leads: warm, service-oriented, existing customer tone
    """
    services_context = _load_services_context()
    is_renewal = lead.get("lead_type") == "renewal"

    original_subject = lead.get("draft_subject", "")
    original_body = lead.get("draft_body", "")
    name = lead.get("name", "")
    city = lead.get("city", "")
    state = lead.get("state", "CT")
    naics = lead.get("naics", "")
    filing_date = lead.get("filing_date", "")
    brave_summary = lead.get("brave_summary", "")
    equipment = lead.get("equipment", {})
    equipment_talk = equipment.get("talk_track", "") if equipment else ""

    if is_renewal:
        lines = [
            "YOU ARE Mark Mazza, replying to an EXISTING CUSTOMER who responded to",
            "your renewal outreach. This is a warm reply -- they already have Xerox",
            "equipment and an existing relationship with ABS.",
            "",
            "RESPONSE PRINCIPLES:",
            "",
            "1. ACKNOWLEDGE what they said. Reference their reply specifically.",
            "2. ANSWER any question they asked about upgrade options, contract terms,",
            "   or pricing. Give ranges where appropriate, not exact figures.",
            "3. REFERENCE their existing equipment model and usage naturally.",
            "4. ONE NEXT STEP. Suggest exactly one concrete next step -- a call,",
            "   a site visit, sending spec sheets. Not three options. One.",
            "5. KEEP IT SHORT. 50-80 words max. They already know who you are.",
            "6. TONE is warm, service-oriented, and professional. This is a customer",
            "   conversation, not a sales pitch.",
            "",
            "NO EM DASHES: Never use em dashes. Use two hyphens (--) instead.",
            "",
        ]
    else:
        lines = [
            "YOU ARE Mark Mazza, replying to someone who responded to your cold outreach.",
            "They have shown interest. Your tone shifts from introduction to conversation.",
            "",
            "RESPONSE PRINCIPLES:",
            "",
            "1. ACKNOWLEDGE what they said. Reference their reply specifically.",
            "2. ANSWER any question they asked. If pricing, give a range (not exact).",
            "3. STAY ON PRODUCT. Physical machines only. No consulting, no IT services.",
            "4. ONE NEXT STEP. Suggest exactly one concrete next step -- a call, a site",
            "   visit, sending specs. Not three options. One.",
            "5. KEEP IT SHORT. 50-80 words max. They already know who you are.",
            "6. NO DIFFERENTIATOR CLOSE. You already used that in the first touch.",
            "",
            "NO EM DASHES: Never use em dashes. Use two hyphens (--) instead.",
            "",
        ]

    if services_context:
        lines.append(services_context)
        lines.append("")

    lines.extend([
        "WHAT YOU ORIGINALLY SENT:",
        f"Subject: {original_subject}",
        f"Body: {original_body}",
        "",
        "THEIR REPLY:",
        reply_text,
        "",
        "ABOUT THIS BUSINESS:",
        f"Name: {name}",
        f"Location: {city}, {state}",
        f"Industry: {naics}",
        f"Filed: {filing_date}",
    ])

    if brave_summary:
        lines.append(f"Description: {brave_summary}")

    if equipment_talk:
        lines.append(f"Equipment context: {equipment_talk}")

    # Renewal-specific lead data
    if is_renewal:
        equipment_model = lead.get("equipment_model", "")
        if equipment_model:
            lines.append(f"Equipment model: {equipment_model}")
        contract_expiration = lead.get("contract_expiration", "")
        if contract_expiration:
            lines.append(f"Contract expiration: {contract_expiration[:10]}")
        upgrade = lead.get("recommended_upgrade", "")
        if upgrade:
            lines.append(f"Recommended upgrade: {upgrade}")

    # Operator intel
    from capabilities.drafting import _build_intel_section
    intel_section = _build_intel_section(lead, batch_intel=batch_intel)
    if intel_section:
        for line in intel_section.strip().split("\n"):
            lines.append(line)
        lines.append("")

    lines.extend([
        "",
        "Generate ONLY the response. Start with 'SUBJECT: Re: [original subject]'",
        "on the first line, then blank line, then body. No preamble.",
        "Keep the body under 80 words.",
    ])

    return "\n".join(lines)


def _parse_response(response: str) -> dict:
    """Parse LLM response into subject/body."""
    raw_lines = response.strip().split("\n")
    subject = ""
    body_start = 0

    for i, line in enumerate(raw_lines):
        if line.upper().startswith("SUBJECT:"):
            subject = line.split(":", 1)[1].strip()
            body_start = i + 1
            break

    while body_start < len(raw_lines) and not raw_lines[body_start].strip():
        body_start += 1

    body = "\n".join(raw_lines[body_start:]).strip()

    if not body:
        body = response.strip()

    subject = subject.replace("\u2014", "--")
    body = body.replace("\u2014", "--")

    return {"subject": subject, "body": body}


def draft_response(lead_id: str, reply_text: str, batch_intel: str | None = None) -> dict:
    """Given a lead and their reply, draft a contextual response.

    Args:
        lead_id: Lead ID
        reply_text: The reply text from the lead
        batch_intel: Optional operator intel for this draft

    Returns:
        Dict with subject, body, context_used
    """
    lead = get_lead(lead_id)
    if not lead:
        raise KeyError(f"Lead {lead_id} not found")

    if not reply_text.strip():
        raise ValueError("Reply text cannot be empty")

    prompt = _build_response_prompt(lead, reply_text, batch_intel=batch_intel)
    response, metadata = call_llm(prompt)
    parsed = _parse_response(response)

    context_used = []
    if lead.get("draft_subject"):
        context_used.append("original_touch")
    if lead.get("brave_summary"):
        context_used.append("brave_summary")
    if (lead.get("equipment") or {}).get("talk_track"):
        context_used.append("equipment_talk_track")
    if lead.get("principal_names"):
        context_used.append("principal_data")

    now = datetime.utcnow().isoformat() + "Z"

    update_lead(lead_id, {
        "reply_received": True,
        "reply_received_at": now,
        "reply_text": reply_text[:500],
        "response_drafted": True,
        "response_body": parsed["body"],
        "response_subject": parsed["subject"],
        "last_updated": now,
    })

    # Auto-pause any active sequence
    from capabilities.sequencing import pause_sequence_on_reply
    try:
        pause_sequence_on_reply(lead_id)
    except (ValueError, KeyError):
        pass  # No active sequence, or already paused

    return {
        "subject": parsed["subject"],
        "body": parsed["body"],
        "context_used": context_used,
    }
