"""Classify finance-assistant questions into response intents."""

from __future__ import annotations

import re
from typing import Literal

from app.pdf.calc import is_pdf_overview_question

FinanceIntent = Literal[
    "statement_summary",
    "spending_summary",
    "merchant_analysis",
    "category_spending",
    "date_lookup",
    "income",
    "transaction_search",
    "comparison",
    "insights",
    "financial_advice",
    "document_qa",
    "period_coverage",
    "payment_count",
    "general",
]

FINANCE_INTENT_LABELS: dict[str, str] = {
    "statement_summary": "Statement Summary",
    "spending_summary": "Spending Summary",
    "merchant_analysis": "Merchant Analysis",
    "category_spending": "Category Spending",
    "date_lookup": "Date Lookup",
    "income": "Income",
    "transaction_search": "Transaction Search",
    "comparison": "Comparison",
    "insights": "Insights",
    "financial_advice": "Financial Advice",
    "document_qa": "Document Q&A",
    "period_coverage": "Period Coverage",
    "payment_count": "Payment Count",
    "general": "General",
}

_CATEGORY_RE = re.compile(
    r"(?i)\b(grocer(?:y|ies)|food|taxi|cab|uber|ola|bills?|recharge|"
    r"transfer|zepto|blinkit|swiggy|zomato|instamart)\b"
)
_MERCHANT_RE = re.compile(
    r"(?i)\b("
    r"merchant|paid\s+to|sent\s+to|who\s+did\s+i\s+pay|"
    r"payments?\s+to|upi\s+to|money\s+sent\s+to"
    r")\b"
)
# Capitalized payee after "to " — MUST stay case-sensitive (do not use (?i)).
_MERCHANT_PROPER_RE = re.compile(
    r"(?<![A-Za-z])to\s+([A-Z][a-zA-Z0-9&.'\-]{2,}(?:\s+[A-Z][a-zA-Z0-9&.'\-]{1,30}){0,2})\b"
)
_DATE_RE = re.compile(
    r"(?i)\b("
    r"\d{1,2}(?:st|nd|rd|th)?\s+(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*"
    r"|(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\s+\d{1,2}"
    r"|each\s+date|per\s+day|by\s+date|daily\s+spend|date[\s-]?wise|day[\s-]?wise"
    r"|on\s+\d{1,2}\b"
    r")\b"
)
_SPEND_RE = re.compile(
    r"(?i)\b("
    r"spent|spend|spen|paid|payments?|pay(?:ing)?|debit|outflow|money\s+out|how\s+much|"
    r"did\s+i\s+(?:make|pay|spend)|any\s+(?:payments?|transactions?|txns?)"
    r")\b"
)
_INCOME_RE = re.compile(
    r"(?i)\b(received|receive|income|credited|credit|inflow|money\s+in|deposit)\b"
)
_SUMMARY_RE = re.compile(
    r"(?i)\b(summary|overview|tell\s+me\s+about|what\s+is\s+this|bank\s+statement|"
    r"knowledge\s*base|passbook|upi\s+statement)\b"
)
_COMPARE_RE = re.compile(
    r"(?i)\b(compar(?:e|ison)|versus|vs\.?|difference\s+between|more\s+than|less\s+than)\b"
)
_INSIGHT_RE = re.compile(
    r"(?i)\b(insight|pattern|trend|unusual|largest|biggest|top\s+\d+|average|avg)\b"
)
_FINANCIAL_ADVICE_RE = re.compile(
    r"(?i)\b("
    r"save\s+money|reduce\s+(?:my\s+)?(?:spend(?:ing)?|expenses?)|"
    r"cut\s+(?:back\s+)?(?:on\s+)?(?:spend(?:ing)?|expenses?)|"
    r"spending\s+too\s+much|spend(?:ing)?\s+too\s+much|"
    r"where\s+(?:should|can)\s+i\s+cut|"
    r"(?:analyze|analyse)\s+(?:my\s+)?(?:habits?|spending)|"
    r"spending\s+habits?|budget\s+(?:tips?|advice)|"
    r"how\s+(?:can|do)\s+i\s+(?:save|budget|spend\s+less)|"
    r"am\s+i\s+(?:over)?spend(?:ing)?"
    r")\b"
)
_BIGGEST_EXPENSES_RE = re.compile(
    r"(?i)\b("
    r"(?:biggest|largest|top|highest|major)\s+(?:expenses?|outflows?|payments?|spends?|transactions?)|"
    r"(?:expenses?|outflows?|payments?)\s+(?:ranked|sorted)|"
    r"show\s+(?:me\s+)?(?:my\s+)?(?:biggest|largest|top)|"
    r"what\s+(?:are|were)\s+(?:my\s+)?(?:biggest|largest)|"
    r"top\s+\d+\s+(?:expenses?|payments?|outflows?)"
    r")\b"
)
_SEARCH_RE = re.compile(
    r"(?i)\b("
    r"find|show|list|search|transactions?\s+(?:with|for|from)|where\s+did|"
    r"any\s+(?:payments?|transactions?)|make\s+any\s+(?:payments?|transactions?)"
    r")\b"
)
_MONTH_WORD_RE = re.compile(
    r"(?i)\b(jan(?:uary)?|feb(?:ruary)?|mar(?:ch)?|apr(?:il)?|may|jun(?:e)?|"
    r"jul(?:y)?|aug(?:ust)?|sep(?:t(?:ember)?)?|oct(?:ober)?|nov(?:ember)?|dec(?:ember)?)\b"
)
_PERIOD_COVERAGE_RE = re.compile(
    r"(?i)\b("
    r"why\s+.+\b(?:only|not|missing|instead|contain|contains|include|includes|cover|covers)\b|"
    r"why\s+not\s+\w+|"
    r"why\s+(?:does|did|is|are)\s+it\b|"
    r"which\s+months?|what\s+(?:period|date\s*range)|date\s*range|"
    r"does\s+it\s+(?:include|contain|cover)|"
    r"only\s+(?:has|have|contains?|includes?|covers?)|"
    r"not\s+(?:include|contain|have|cover)\s+\w+|"
    r"period\s+(?:covered|on\s+the\s+statement)|months?\s+covered"
    r")\b"
)
_PAYMENT_LIST_RE = re.compile(
    r"(?i)\b("
    r"which\s+payments?\s+(?:did\s+i|have\s+i|was|were|do\s+i)|"
    r"what\s+payments?\s+(?:did\s+i|have\s+i|was|were|do\s+i)|"
    r"list\s+(?:all\s+|every\s+)?(?:my\s+)?payments?|"
    r"full\s+list\s+of\s+payments?|"
    r"every\s+payment|all\s+my\s+payments?|"
    r"show\s+(?:me\s+)?(?:all\s+)?(?:my\s+)?payments?|"
    r"payments?\s+i\s+made|my\s+payments?|"
    r"payments?\s+in\s+a\s+paragraph|need\s+(?:the\s+)?(?:full\s+)?list\s+of\s+payments?"
    r")\b"
)
_PAYMENT_SINGULAR_CLARIFY_RE = re.compile(r"(?i)\bwhich payment\b(?!\s*s\b)")
_PAYMENT_COUNT_RE = re.compile(
    r"(?i)\b("
    r"how\s+many\s+(?:transactions?|payments?|txns?|credits?|deposits?|debits?|withdrawals?)"
    r"(?:\s+\w+){0,6}"
    r"|number\s+of\s+(?:transactions?|payments?|credits?|deposits?|debits?)"
    r"|payments?\s+(?:were\s+|are\s+)?(?:made|received)"
    r"|credits?\s+(?:were\s+|are\s+)?(?:made|received|,?\s*ade)"
    r"|deposits?\s+(?:were\s+|are\s+)?(?:made|received)"
    r"|transactions?\s+(?:were\s+|are\s+)?made"
    r"|transaction\s+count"
    r"|payment\s+count"
    r"|credit\s+count"
    r")\b"
)
_STATEMENT_FIELD_RE = re.compile(
    r"(?i)\b("
    r"branch\s*(?:code|number|no\.?|num)?|"
    r"account\s*(?:branch|number|no\.?|num)|"
    r"a/?c\s*(?:number|no\.?|num)|"
    r"ifsc(?:\s*code)?|micr(?:\s*code)?|"
    r"cust(?:omer)?\s*id|"
    r"state\s+(?:on\s+)?(?:the\s+)?(?:account|statement)|"
    r"account\s+state|"
    r"state\s+account\s+branch|"
    r"statement\s+account\s+branch|"
    r"account\s+branch(?:\s+(?:code|number|name))?|"
    r"(?:my\s+)?bank\s+name|name\s+of\s+(?:the\s+)?bank|which\s+bank|"
    r"what\s+(?:bank|bank\s+is\s+this)"
    r")\b"
)
_STATEMENT_IDENTITY_RE = re.compile(
    r"(?i)\b("
    r"whose\s+statement|account\s+holder|customer\s+name|"
    r"who(?:se)?\s+(?:is\s+this\s+statement|owns\s+this)|"
    r"name\s+on\s+(?:the\s+)?statement|statement\s+(?:belongs|for\s+whom)|"
    r"(?:what(?:'s|\s+is)|guess|infer|think)\s+my\s+name|"
    r"do\s+you\s+think\s+(?:is\s+)?my\s+name|"
    r"what\s+do\s+you\s+think\s+(?:is\s+)?my\s+name|"
    r"my\s+name\s+(?:from|on|per|in)\s+(?:the\s+)?(?:statement|pdf|document|bank|knowledge\s*base)|"
    r"name\s+(?:from|on|in)\s+(?:the\s+)?(?:statement|pdf|bank\s+statement|knowledge\s*base)|"
    r"(?:based\s+on|from|using)\s+(?:the\s+)?(?:knowledge\s*base|statement|pdf|document).{0,40}my\s+name|"
    r"my\s+name.{0,40}(?:based\s+on|from|using)\s+(?:the\s+)?(?:knowledge\s*base|statement|pdf|document)"
    r")\b"
)
_PROFIT_LOSS_RE = re.compile(
    r"(?i)\b("
    r"profit\s+or\s+loss|loss\s+or\s+profit|"
    r"in\s+(?:a\s+)?(?:profit|loss)|"
    r"am\s+i\s+(?:in\s+)?(?:profit|loss)|"
    r"(?:making|made)\s+(?:a\s+)?(?:profit|loss)|"
    r"net\s+cash\s*-?\s*flow|cash\s*-?\s*flow\s+(?:positive|negative)|"
    r"in\s+the\s+(?:red|black)|"
    r"did\s+i\s+(?:profit|lose|come\s+out\s+ahead)"
    r")\b"
)
_STATEMENT_PERIOD_RE = re.compile(
    r"(?i)\b("
    r"statement\s+period|what(?:'s|\s+is|\s+was)\s+the\s+period|"
    r"period\s+of\s+(?:the\s+)?statement|covering\s+(?:which\s+)?(?:period|dates?)|"
    r"from\s+when\s+to\s+when|date\s*range\s+of\s+(?:the\s+)?statement"
    r")\b"
)
_FIRST_PERSON_RE = re.compile(
    r"(?i)\b(i|my|me|mine|our|we|us|"
    r"this\s+statement|the\s+statement|my\s+statement|"
    r"uploaded|knowledge\s*base|passbook)\b"
)
_THIRD_PARTY_SPEND_RE = re.compile(
    r"(?i)\bhow\s+much\s+(?:did|does|has)\s+(?!i\b|we\b|you\b)(.+?)\s+"
    r"(?:spend|spent|pay|paid)\b"
)
# Stop-words that should not be treated as merchant names in "on X" / "to X".
_MERCHANT_STOP = {
    "grocery",
    "groceries",
    "food",
    "taxi",
    "cab",
    "bills",
    "bill",
    "transfer",
    "transfers",
    "recharge",
    "total",
    "average",
    "this",
    "that",
    "my",
    "the",
    "a",
    "an",
    "last",
    "month",
    "year",
    "week",
    "day",
    "date",
    "upi",
    "statement",
    "account",
    "money",
    "payment",
    "payments",
    "transaction",
    "transactions",
}


def is_financial_advice_question(question: str) -> bool:
    return bool(_FINANCIAL_ADVICE_RE.search(question or ""))


def is_period_coverage_question(question: str) -> bool:
    q = (question or "").strip()
    if not q:
        return False
    if _PERIOD_COVERAGE_RE.search(q):
        return True
    months = _MONTH_WORD_RE.findall(q)
    if len(months) >= 2 and re.search(r"(?i)\b(only|not|why|contain|include|cover|missing)\b", q):
        return True
    if len(months) >= 1 and re.search(
        r"(?i)\b(only\s+contain|only\s+includes?|why\s+only|not\s+august|not\s+july)\b",
        q,
    ):
        return True
    return False


def is_payment_list_question(question: str) -> bool:
    return bool(_PAYMENT_LIST_RE.search(question or ""))


def is_analyse_overview_ask(question: str) -> bool:
    """True for analyse/summarize the document — not spend totals or payment lists."""
    q = (question or "").strip()
    if not re.search(r"(?i)\b(analy[sz]e|overview|summar(?:y|ize))\b", q):
        return False
    if is_payment_list_question(q):
        return False
    if re.search(r"(?i)\b(how\s+much|how\s+many|list\s+all|total\s+spent)\b", q):
        return False
    if _SPEND_RE.search(q) and not re.search(r"(?i)\banaly[sz]e\b", q):
        return False
    return True


def is_vague_single_payment_question(question: str) -> bool:
    return bool(_PAYMENT_SINGULAR_CLARIFY_RE.search(question or ""))


def is_payment_count_question(question: str) -> bool:
    return bool(_PAYMENT_COUNT_RE.search(question or ""))


def is_statement_identity_question(question: str) -> bool:
    q = question or ""
    if _STATEMENT_IDENTITY_RE.search(q):
        return True
    # "this is my bank statement, what do you think my name is"
    if re.search(r"(?i)\b(?:bank\s+)?statement\b", q) and re.search(
        r"(?i)\b(?:my\s+name|account\s+holder|who\s+am\s+i)\b", q
    ):
        return True
    return False


def is_profit_loss_question(question: str) -> bool:
    return bool(_PROFIT_LOSS_RE.search(question or ""))


def is_biggest_expenses_question(question: str) -> bool:
    """True for ranked expense lists — not a restatement of header totals."""
    return bool(_BIGGEST_EXPENSES_RE.search(question or ""))


def is_statement_field_question(question: str) -> bool:
    """Branch / IFSC / account number / MICR / state on the uploaded statement."""
    return bool(_STATEMENT_FIELD_RE.search(question or ""))


def is_statement_period_question(question: str) -> bool:
    q = question or ""
    if _STATEMENT_PERIOD_RE.search(q):
        return True
    return bool(
        re.search(r"(?i)\bwhat\s+(?:is|was)\s+the\s+(?:statement\s+)?period\b", q)
        or re.search(r"(?i)\bstatement\s+period\b", q)
    )


def is_unrelated_party_spend_question(question: str) -> bool:
    """True when the user asks about someone else's spend (not their uploaded statement)."""
    q = (question or "").strip()
    if not q:
        return False
    if _FIRST_PERSON_RE.search(q):
        return False
    m = _THIRD_PARTY_SPEND_RE.search(q)
    if not m:
        return False
    subject = re.sub(r"\s+", " ", m.group(1)).strip().lower()
    if not subject or subject in {"i", "we", "you", "someone", "anybody", "anyone"}:
        return False
    if subject in _MERCHANT_STOP:
        return False
    return True


def is_analytical_self_contained(question: str) -> bool:
    """True when the question fully specifies its own finance ask (not a pronoun follow-up)."""
    q = (question or "").strip()
    if not q:
        return False
    # Pronoun-heavy shorts are follow-ups, not self-contained.
    if re.search(r"(?i)\b(it|that|this|those|these|them|previous|above)\b", q) and len(q.split()) <= 10:
        if not _DATE_RE.search(q) and not _CATEGORY_RE.search(q) and not extract_merchant_query(q):
            return False
    intent = classify_finance_intent(q)
    return intent in {
        "spending_summary",
        "category_spending",
        "date_lookup",
        "income",
        "payment_count",
        "merchant_analysis",
        "statement_summary",
        "insights",
        "period_coverage",
        "transaction_search",
    }


def extract_merchant_query(question: str) -> str | None:
    """Pull a merchant/payee name from 'spent on X' / 'paid to X' / 'pay Nykaa' phrasing."""
    q = (question or "").strip()
    if not q:
        return None
    patterns = [
        r"(?i)spend(?:ing)?\s+(?:this\s+much\s+|so\s+much\s+|too\s+much\s+)?on\s+"
        r"([A-Za-z0-9][A-Za-z0-9&.'\-]{1,40}(?:\s+[A-Za-z0-9&.'\-]{1,30}){0,3})",
        r"(?i)(?:spent|spend|paid|pay|payment)\s+on\s+"
        r"([A-Za-z0-9][A-Za-z0-9&.'\-]{1,40}(?:\s+[A-Za-z0-9&.'\-]{1,30}){0,3})",
        r"(?i)(?:cut|reduce|trim|save)\s+(?:back\s+)?on\s+(?:my\s+)?"
        r"([A-Za-z0-9][A-Za-z0-9&.'\-]{1,40}(?:\s+[A-Za-z0-9&.'\-]{1,30}){0,3})",
        r"(?i)(?:how\s+much\s+(?:did\s+i\s+)?)?(?:paid|pay|sent|send)\s+(?:to\s+)?"
        r"([A-Za-z0-9][A-Za-z0-9&.'\-]{1,40}(?:\s+[A-Za-z0-9&.'\-]{1,30}){0,3})",
        r"(?i)money\s+sent\s+to\s+([A-Za-z0-9][A-Za-z0-9&.'\-]{1,40}(?:\s+[A-Za-z0-9&.'\-]{1,30}){0,3})",
        r"(?i)payments?\s+to\s+([A-Za-z0-9][A-Za-z0-9&.'\-]{1,40}(?:\s+[A-Za-z0-9&.'\-]{1,30}){0,3})",
        # Trailing "… on Zomato" / "… at Zepto" (advice follow-ups)
        r"(?i)\b(?:on|at)\s+([A-Za-z][A-Za-z0-9&.'\-]{2,40})\s*[?.!]*$",
    ]
    for pat in patterns:
        m = re.search(pat, q)
        if not m:
            continue
        raw = m.group(1).strip(" ?.,!;:\"'")
        raw = re.sub(
            r"(?i)\s+(last|this|previous|in|on|during|for)\s+(month|year|week|july|august|june).*$",
            "",
            raw,
        ).strip()
        tokens = [t for t in re.split(r"\s+", raw) if t]
        while tokens and tokens[-1].lower() in {
            "last",
            "this",
            "month",
            "year",
            "week",
            "total",
            "please",
        }:
            tokens.pop()
        if not tokens:
            continue
        if all(t.lower() in _MERCHANT_STOP for t in tokens):
            continue
        if tokens[0].lower() in _MERCHANT_STOP and len(tokens) == 1:
            continue
        name = " ".join(tokens).strip()
        if len(name) < 2:
            continue
        return name
    return None


def classify_finance_intent(question: str) -> FinanceIntent:
    q = (question or "").strip()
    if not q:
        return "general"
    # Light typo repair so "credits were ,ade" still counts as a count ask
    q = re.sub(r"(?i)\b,\s*ade\b", " made", q)

    # Order matters — more specific first.
    if is_unrelated_party_spend_question(q):
        return "general"
    if _FINANCIAL_ADVICE_RE.search(q):
        return "financial_advice"
    if is_period_coverage_question(q):
        return "period_coverage"
    if is_profit_loss_question(q):
        return "insights"
    if is_biggest_expenses_question(q):
        return "insights"
    if is_statement_identity_question(q) or is_statement_period_question(q):
        return "statement_summary"
    if is_statement_field_question(q):
        return "statement_summary"
    if is_payment_count_question(q) and (
        _DATE_RE.search(q)
        or re.search(
            r"(?i)\b\d{1,2}(?:st|nd|rd|th)?\s+"
            r"(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\b",
            q,
        )
    ):
        return "date_lookup"
    if is_payment_count_question(q):
        return "payment_count"
    if is_pdf_overview_question(q) or is_analyse_overview_ask(q):
        return "statement_summary"
    if _COMPARE_RE.search(q) and re.search(
        r"(?i)\b("
        r"spend|spent|paid|payment|received|merchant|statement|category|"
        r"month|july|august|june|total|outflow|inflow|this\s+pdf|uploaded"
        r")\b",
        q,
    ):
        return "comparison"
    if _DATE_RE.search(q) and (_SPEND_RE.search(q) or _INCOME_RE.search(q) or _SEARCH_RE.search(q)):
        return "date_lookup"
    # Explicit calendar day + activity even when phrasing is unusual
    if re.search(
        r"(?i)\b\d{1,2}(?:st|nd|rd|th)?\s+"
        r"(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\b"
        r"|(?:jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)[a-z]*\s+\d{1,2}\b",
        q,
    ) and re.search(
        r"(?i)\b(payments?|transactions?|spent|spend|paid|pay|activity|outflow|inflow)\b",
        q,
    ):
        return "date_lookup"
    if _CATEGORY_RE.search(q) and (_SPEND_RE.search(q) or "on " in q.lower()):
        return "category_spending"
    merchant = extract_merchant_query(q)
    if merchant and (_SPEND_RE.search(q) or _MERCHANT_RE.search(q) or _MERCHANT_PROPER_RE.search(q)):
        return "merchant_analysis"
    # Bare "to Nykaa" only counts with spend/payment cues — never "how to plant a tree".
    if _MERCHANT_RE.search(q) and (_SPEND_RE.search(q) or _FIRST_PERSON_RE.search(q)):
        return "merchant_analysis"
    if _MERCHANT_PROPER_RE.search(q) and (_SPEND_RE.search(q) or re.search(r"(?i)\b(paid|pay|sent|send|upi)\b", q)):
        return "merchant_analysis"
    # Month-scoped spend (e.g. "how much did I spend in August") — not whole-statement dump
    if (
        _MONTH_WORD_RE.search(q)
        or re.search(r"(?i)\b(last|previous|this|current)\s+month\b", q)
    ) and _SPEND_RE.search(q):
        return "date_lookup"
    if _INCOME_RE.search(q):
        return "income"
    if is_payment_list_question(q):
        return "transaction_search"
    if _SPEND_RE.search(q):
        return "spending_summary"
    if _INSIGHT_RE.search(q):
        return "insights"
    if _SUMMARY_RE.search(q):
        return "statement_summary"
    if _SEARCH_RE.search(q):
        return "transaction_search"
    # Generic "what is a PDF?" is a concept — not document Q&A
    if re.search(r"(?i)^\s*what\s+is\s+a\s+pdf\b", q):
        return "general"
    if re.search(r"(?i)\b(my\s+)?(pdf|document|statement|invoice|page)\b", q) and not re.search(
        r"(?i)^\s*what\s+is\s+(?:a|an)\s+", q
    ):
        return "document_qa"
    return "general"
