from __future__ import annotations

import re
from pathlib import Path

import pandas as pd

from .base import BaseParser, StatementMeta, amount, extract_text, finalize, norm
from .common import date_iso, find, infer_mode


class BankOfBarodaParser(BaseParser):
    bank_name = "Bank of Baroda"
    parser_name = "BankOfBarodaParser"

    _DATE = re.compile(r"^(\d{2}-\d{2}-\d{2})\s+(.*)$")
    _BALANCE = re.compile(r"(-?[0-9,]+\.\d{2})(Cr|Dr)\s*$", re.I)
    _MONEY = re.compile(r"(?<![A-Za-z0-9])([0-9,]+\.\d{2})(?![A-Za-z0-9])")

    @classmethod
    def detect(cls, text: str) -> float:
        upper = (text or "").upper()
        score = 0.0
        if "BANK OF BARODA" in upper:
            score += 0.62
        if "BARB0" in upper or "IFSC CODE: BARB" in upper:
            score += 0.20
        if all(token in upper for token in ("DATE", "PARTICULARS", "CHQ.NO.", "WITHDRAWALS", "DEPOSITS", "BALANCE")):
            score += 0.16
        if "BOB ADVANTAGE" in upper:
            score += 0.02
        return min(score, 0.99)

    @staticmethod
    def _signed_balance(value: str, marker: str) -> float | None:
        parsed = amount(value)
        if parsed is None:
            return None
        return -abs(parsed) if marker.upper() == "DR" else abs(parsed)

    @staticmethod
    def _reference_number(narration: str, cheque_no: str) -> str:
        if norm(cheque_no):
            return norm(cheque_no)
        for pattern in (
            r"\bNEFT[-/]([A-Z0-9]{10,30})\b",
            r"\bRTGS[-/]([A-Z0-9]{10,30})\b",
            r"\b([0-9]{12,18})\b",
        ):
            match = re.search(pattern, narration, re.I)
            if match:
                return match.group(1)
        return ""

    def parse(self, path, text=None):
        pdf_path = Path(path)
        text = text or extract_text(pdf_path)
        meta = StatementMeta(
            bank_name=self.bank_name,
            source_file=pdf_path.name,
            parser_name=self.parser_name,
            confidence="High",
        )
        meta.customer_name = norm(find(r"A/C Name\s*:\s*([^\n]+)", text, flags=re.I))
        meta.account_number = find(r"A/C Number\s*:\s*([0-9]+)", text, flags=re.I)
        meta.ifsc = find(r"IFSC CODE:\s*([A-Z0-9]+)", text, flags=re.I)
        period = re.search(r"Statement of account for the period of\s+(\d{2}-\d{2}-\d{4})\s+to\s+(\d{2}-\d{2}-\d{4})", text, re.I)
        if period:
            meta.period_from = date_iso(period.group(1))
            meta.period_to = date_iso(period.group(2))

        rows: list[dict] = []
        current: dict | None = None
        previous_balance: float | None = None
        opening_balance: float | None = None

        pages = re.split(r"\f", text)
        for page_no, page_text in enumerate(pages, start=1):
            for raw_line in page_text.splitlines():
                line = raw_line.rstrip()
                match = self._DATE.match(line.strip())
                if match:
                    if current is not None:
                        rows.append(current)
                        previous_balance = current["balance"]
                        current = None
                    txn_date, remainder = match.groups()
                    balance_match = self._BALANCE.search(remainder)
                    if not balance_match:
                        continue
                    balance_value = self._signed_balance(balance_match.group(1), balance_match.group(2))
                    before_balance = remainder[:balance_match.start()].rstrip()
                    if norm(before_balance).upper() == "B/F":
                        opening_balance = balance_value
                        previous_balance = balance_value
                        continue
                    money_matches = list(self._MONEY.finditer(before_balance))
                    narration_part = before_balance
                    cheque_no = ""
                    if money_matches:
                        narration_part = before_balance[:money_matches[0].start()].rstrip()
                    cheque_match = re.search(r"\s([0-9]{1,8})\s*$", narration_part)
                    if cheque_match:
                        cheque_no = cheque_match.group(1)
                        narration_part = narration_part[:cheque_match.start()].rstrip()
                    debit_value = credit_value = None
                    if previous_balance is not None and balance_value is not None:
                        delta = round(balance_value - previous_balance, 2)
                        if delta < -0.009:
                            debit_value = abs(delta)
                        elif delta > 0.009:
                            credit_value = delta
                    elif money_matches:
                        debit_value = amount(money_matches[-1].group(1))
                    current = {
                        "transaction_date": txn_date,
                        "value_date": txn_date,
                        "narration": norm(narration_part),
                        "reference_no": self._reference_number(norm(narration_part), cheque_no),
                        "debit": debit_value,
                        "credit": credit_value,
                        "balance": balance_value,
                        "source_page": page_no,
                        "mode": infer_mode(norm(narration_part)),
                    }
                    continue

                if current is not None:
                    continuation = norm(line)
                    upper = continuation.upper()
                    if not continuation or upper.startswith(("PAGE TOTAL", "GRAND TOTAL", "BANK OF BARODA", "A/C NUMBER", "STATEMENT OF ACCOUNT", "DATE PARTICULARS", "NOTE:", "UNLESS ")):
                        continue
                    if set(continuation) <= {"-"}:
                        continue
                    current["narration"] = norm(f"{current['narration']} {continuation}")
                    current["reference_no"] = self._reference_number(current["narration"], current["reference_no"])
                    current["mode"] = infer_mode(current["narration"])

        if current is not None:
            rows.append(current)

        data = pd.DataFrame(rows)
        if not data.empty:
            first = data.iloc[0]
            first_balance = float(first["balance"])
            first_debit = float(first["debit"]) if pd.notna(first["debit"]) else 0.0
            first_credit = float(first["credit"]) if pd.notna(first["credit"]) else 0.0
            meta.opening_balance = round(opening_balance if opening_balance is not None else first_balance + first_debit - first_credit, 2)
            meta.total_debit = round(float(pd.to_numeric(data["debit"], errors="coerce").fillna(0).sum()), 2)
            meta.total_credit = round(float(pd.to_numeric(data["credit"], errors="coerce").fillna(0).sum()), 2)
            meta.closing_balance = round(float(data.iloc[-1]["balance"]), 2)
        return meta, finalize(data, meta)
