from __future__ import annotations

import re
from datetime import date, datetime
from decimal import Decimal, InvalidOperation
from typing import Any

CITI_BILLING_PERIOD_RE = re.compile(r"Billing\s+Period:\s*(\d{2}/\d{2}/\d{2})-(\d{2}/\d{2}/\d{2})")
CITI_ACCOUNT_ENDING_RE = re.compile(r"Account number ending in:?\s*(\d{4})", re.IGNORECASE)
MMDD_RE = re.compile(r"^\d{2}/\d{2}$")
AMOUNT_ONLY_RE = re.compile(r"^\$\d[\d,]*\.\d{2}$")


def detect(full_text: str) -> float:
    """Detect if this is a Citi credit card statement."""
    score = 0.0
    text = full_text.upper()
    
    if "CITI" in text or "CITIBANK" in text:
        score += 0.3
    if CITI_BILLING_PERIOD_RE.search(full_text):
        score += 0.3
    if "Payments, Credits and Adjustments" in full_text or "Standard Purchases" in full_text:
        score += 0.25
    if CITI_ACCOUNT_ENDING_RE.search(full_text):
        score += 0.1
    
    return min(score, 1.0)


def _clean_line(raw: str) -> str:
    """Normalize whitespace in a line."""
    return " ".join(raw.split())


def _parse_date(date_str: str, fmt: str) -> date:
    """Parse a date string, handling errors gracefully."""
    try:
        return datetime.strptime(date_str, fmt).date()
    except ValueError:
        return date.today()


def _parse_decimal(token: str) -> Decimal:
    """Parse a decimal value from a token."""
    normalized = token.replace("$", "").replace(",", "")
    try:
        return Decimal(normalized)
    except (InvalidOperation, ValueError):
        return Decimal("0")


def _parse_statement_period(full_text: str) -> tuple[date, date]:
    """Extract statement period from Citi statement."""
    match = CITI_BILLING_PERIOD_RE.search(full_text)
    if match:
        start_dt = _parse_date(match.group(1), "%m/%d/%y")
        end_dt = _parse_date(match.group(2), "%m/%d/%y")
        return start_dt, end_dt
    
    # Fallback
    today = date.today()
    return today, today


def _infer_post_date(post_mmdd: str, statement_start: date, statement_end: date) -> date:
    """Infer full date from MM/DD given the statement period."""
    try:
        month = int(post_mmdd[:2])
        day = int(post_mmdd[3:5])
    except (ValueError, IndexError):
        return date.today()
    
    if statement_start.year == statement_end.year:
        year = statement_start.year
    else:
        year = statement_start.year if month >= statement_start.month else statement_end.year
    
    try:
        return date(year, month, day)
    except ValueError:
        return date.today()


def _parse_account_number(full_text: str) -> str:
    """Extract account number (last 4 digits) from statement."""
    match = CITI_ACCOUNT_ENDING_RE.search(full_text)
    if match:
        return match.group(1)
    return ""


def parse(full_text: str, source_file: str) -> dict[str, Any]:
    """Parse Citi credit card statement and extract transactions."""
    statement_start, statement_end = _parse_statement_period(full_text)
    account_last4 = _parse_account_number(full_text)
    
    transactions = []
    
    # Find the start of transaction data
    stop_prefixes = (
        "Fees charged",
        "Interest charged",
        "ThankYou Points Earned",
        "totals year-to-date",
        "Account messages",
        "www.universalcard.com",
        "Customer Service ",
        "Page ",
    )
    skip_prefixes = (
        "Payments, Credits and Adjustments",
        "Standard Purchases",
        "Account Summary",
        "Trans.",
        "Post",
        "date",
        "Description",
        "Amount",
    )
    
    raw_lines = [_clean_line(line) for line in full_text.splitlines()]
    lines = [line for line in raw_lines if line]
    
    start_idx = None
    for idx, line in enumerate(lines):
        if line == "Payments, Credits and Adjustments":
            start_idx = idx
            break
    
    if start_idx is None:
        return {
            "statement_start": statement_start.isoformat(),
            "statement_end": statement_end.isoformat(),
            "account_number": account_last4,
            "transactions": [],
        }
    
    # Collect table lines
    table_lines: list[str] = []
    for line in lines[start_idx:]:
        if any(line.startswith(prefix) for prefix in stop_prefixes):
            break
        if line in {"-"} or MMDD_RE.match(line) or AMOUNT_ONLY_RE.match(line):
            table_lines.append(line)
            continue
        if any(line.startswith(prefix) for prefix in skip_prefixes):
            continue
        table_lines.append(line)
    
    # Parse transactions
    i = 0
    while i < len(table_lines):
        if not MMDD_RE.match(table_lines[i]):
            i += 1
            continue
        
        trans_mmdd = table_lines[i]
        i += 1
        post_mmdd = trans_mmdd
        if i < len(table_lines) and MMDD_RE.match(table_lines[i]):
            post_mmdd = table_lines[i]
            i += 1
        
        desc_parts: list[str] = []
        while i < len(table_lines):
            token = table_lines[i]
            if token == "-" or AMOUNT_ONLY_RE.match(token) or MMDD_RE.match(token):
                break
            desc_parts.append(token)
            i += 1
        
        is_negative = False
        if i < len(table_lines) and table_lines[i] == "-":
            is_negative = True
            i += 1
        
        if i >= len(table_lines) or not AMOUNT_ONLY_RE.match(table_lines[i]):
            continue
        
        amount = _parse_decimal(table_lines[i])
        i += 1
        if is_negative:
            amount = -amount
        
        description = " ".join(desc_parts).strip()
        if not description:
            continue
        
        post_date = _infer_post_date(post_mmdd, statement_start, statement_end)
        transactions.append({
            "post_date": post_date.isoformat(),
            "description": description,
            "amount": float(amount),
            "balance": None,
            "row_confidence": 0.88,
            "review_reason": "",
        })
    
    return {
        "statement_start": statement_start.isoformat(),
        "statement_end": statement_end.isoformat(),
        "account_number": account_last4,
        "transactions": transactions,
    }
