from __future__ import annotations

from pathlib import Path
import re

import pandas as pd

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


class AndhraPradeshGrameenaBankParser(BaseParser):
    bank_name = "Andhra Pradesh Grameena Bank"
    parser_name = "AndhraPradeshGrameenaBankParser"

    _DATE_LINE = re.compile(r"^\s*(\d{2}-\d{2}-\d{4})\s+(.*)$")
    _BALANCE = re.compile(r"(-?[0-9,]+\.\d{2})\s*(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 "ANDHRA PRADESH GRAMEENA BANK" in upper:
            score += 0.78
        if "COREBANKING.APGB.IN" in upper or "APGB.IN" in upper:
            score += 0.14
        if "UBIN0CG" in upper:
            score += 0.06
        if "STATEMENT OF ACCOUNT" in upper and "PAGE TOTAL" in upper and "GRAND TOTAL" in upper:
            score += 0.02
        return min(score, 0.999)

    @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(narration: str) -> str:
        for pattern in (
            r"\bCMP\d{12,}\b",
            r"\bNACH[A-Z0-9/\-]+\b",
            r"\b\d{12,18}\b",
        ):
            match = re.search(pattern, narration, re.I)
            if match:
                return match.group(0)
        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"Name\s*:\s*([^\n]+?)(?:\s+A/C\s+NO|$)", text, flags=re.I))
        meta.account_number = find(r"A/C\s*NO\s*:\s*([0-9]+)", text, flags=re.I)
        meta.customer_id = find(r"Cust\s*ID\s*:\s*([0-9]+)", text, flags=re.I)
        meta.ifsc = find(r"IFSC\s*CODE\s*:\s*([A-Z0-9]+)", text, flags=re.I)
        period = re.search(r"STATEMENT\s+OF\s+ACCOUNT\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))

        bf = re.search(r"\bB/F\s+([0-9,]+\.\d{2})\s*(Cr|Dr)", text, re.I)
        if bf:
            meta.opening_balance = self._signed_balance(bf.group(1), bf.group(2))

        grand_total = re.search(
            r"Grand\s+Total\s*:\s*([0-9,]+\.\d{2})\s+([0-9,]+\.\d{2})\s+([0-9,]+\.\d{2})\s*(Cr|Dr)",
            text,
            re.I,
        )
        if grand_total:
            meta.total_debit = amount(grand_total.group(1))
            meta.total_credit = amount(grand_total.group(2))
            meta.closing_balance = self._signed_balance(grand_total.group(3), grand_total.group(4))

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

        for page_no, page_text in enumerate(re.split(r"\f", text), start=1):
            for raw_line in page_text.splitlines():
                stripped = raw_line.strip()
                match = self._DATE_LINE.match(stripped)
                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()
                    money_matches = list(self._MONEY.finditer(before_balance))
                    narration_end = money_matches[0].start() if money_matches else len(before_balance)
                    narration = norm(before_balance[:narration_end])

                    debit = credit = 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 = abs(delta)
                        elif delta > 0.009:
                            credit = delta
                    elif money_matches:
                        # The statement prints debit before credit.  The common
                        # running-balance validator will independently verify it.
                        debit = amount(money_matches[-1].group(1))

                    current = {
                        "transaction_date": txn_date,
                        "value_date": txn_date,
                        "narration": narration,
                        "reference_no": self._reference(narration),
                        "debit": debit,
                        "credit": credit,
                        "balance": balance_value,
                        "source_page": page_no,
                        "mode": infer_mode(narration),
                    }
                    continue

                if current is not None:
                    continuation = norm(stripped)
                    upper = continuation.upper()
                    if not continuation:
                        continue
                    if (
                        upper.startswith((
                            "PAGE TOTAL", "GRAND TOTAL", "TRANSACTION DETAILS", "HTTPS://",
                            "UNLESS THE", "BRANCH MANAGER", "****END", "MUCH MORE",
                        ))
                        or set(continuation) <= {"-"}
                        or "STATEMENT OF ACCOUNT,IT WILL BE TAKEN" in upper
                    ):
                        continue
                    current["narration"] = norm(f"{current['narration']} {continuation}")
                    current["reference_no"] = current["reference_no"] or self._reference(current["narration"])

            # Do not carry an unfinished row into footer text on another page;
            # append it at the page boundary while preserving its source page.
            if current is not None:
                rows.append(current)
                previous_balance = current["balance"]
                current = None

        df = pd.DataFrame(rows)
        result = finalize(df, meta)
        if result.empty:
            raise ValueError(
                "Andhra Pradesh Grameena Bank format was identified, but no transaction rows could be extracted."
            )
        return meta, result
