from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
import re
from typing import Iterable


@dataclass(frozen=True)
class BankIdentity:
    name: str
    category: str
    aliases: tuple[str, ...] = ()
    ifsc_prefixes: tuple[str, ...] = ()
    domains: tuple[str, ...] = ()


@dataclass(frozen=True)
class BankIdentityMatch:
    name: str
    category: str
    confidence: float
    evidence: tuple[str, ...]


# Commercial-bank identity registry.  Parsing is intentionally independent of
# this list: templates extract transactions; this registry only assigns the
# institution name.  The list covers current public/private banks, SFBs,
# payments banks, RRBs after the 2025 One-State-One-RRB amalgamation, major
# foreign banks operating in India, and legacy names that still appear in PDFs.
BANK_IDENTITIES: tuple[BankIdentity, ...] = (
    # Public sector banks
    BankIdentity("State Bank of India", "Public Sector Bank", ("STATE BANK OF INDIA", "SBI"), ("SBIN",), ("sbi.co.in", "onlinesbi.sbi")),
    BankIdentity("Bank of Baroda", "Public Sector Bank", ("BANK OF BARODA", "BOB"), ("BARB",), ("bankofbaroda.in",)),
    BankIdentity("Bank of India", "Public Sector Bank", ("BANK OF INDIA",), ("BKID",), ("bankofindia.co.in",)),
    BankIdentity("Bank of Maharashtra", "Public Sector Bank", ("BANK OF MAHARASHTRA",), ("MAHB",), ("bankofmaharashtra.in",)),
    BankIdentity("Canara Bank", "Public Sector Bank", ("CANARA BANK",), ("CNRB",), ("canarabank.com",)),
    BankIdentity("Central Bank of India", "Public Sector Bank", ("CENTRAL BANK OF INDIA",), ("CBIN",), ("centralbankofindia.co.in",)),
    BankIdentity("Indian Bank", "Public Sector Bank", ("INDIAN BANK",), ("IDIB",), ("indianbank.in",)),
    BankIdentity("Indian Overseas Bank", "Public Sector Bank", ("INDIAN OVERSEAS BANK", "IOB"), ("IOBA",), ("iob.in",)),
    BankIdentity("Punjab & Sind Bank", "Public Sector Bank", ("PUNJAB & SIND BANK", "PUNJAB AND SIND BANK"), ("PSIB",), ("punjabandsindbank.co.in",)),
    BankIdentity("Punjab National Bank", "Public Sector Bank", ("PUNJAB NATIONAL BANK", "PNB"), ("PUNB",), ("pnbindia.in",)),
    BankIdentity("UCO Bank", "Public Sector Bank", ("UCO BANK",), ("UCBA",), ("ucobank.com",)),
    BankIdentity("Union Bank of India", "Public Sector Bank", ("UNION BANK OF INDIA",), ("UBIN",), ("unionbankofindia.co.in",)),

    # Indian private sector banks
    BankIdentity("Axis Bank", "Private Sector Bank", ("AXIS BANK",), ("UTIB",), ("axisbank.com",)),
    BankIdentity("Bandhan Bank", "Private Sector Bank", ("BANDHAN BANK",), ("BDBL",), ("bandhanbank.com",)),
    BankIdentity("CSB Bank", "Private Sector Bank", ("CSB BANK", "CATHOLIC SYRIAN BANK"), ("CSBK",), ("csb.co.in",)),
    BankIdentity("City Union Bank", "Private Sector Bank", ("CITY UNION BANK",), ("CIUB",), ("cityunionbank.com",)),
    BankIdentity("DCB Bank", "Private Sector Bank", ("DCB BANK", "DEVELOPMENT CREDIT BANK"), ("DCBL",), ("dcbbank.com",)),
    BankIdentity("Dhanlaxmi Bank", "Private Sector Bank", ("DHANLAXMI BANK", "DHANALAKSHMI BANK"), ("DLXB",), ("dhanbank.com",)),
    BankIdentity("Federal Bank", "Private Sector Bank", ("FEDERAL BANK",), ("FDRL",), ("federalbank.co.in",)),
    BankIdentity("HDFC Bank", "Private Sector Bank", ("HDFC BANK",), ("HDFC",), ("hdfcbank.com",)),
    BankIdentity("ICICI Bank", "Private Sector Bank", ("ICICI BANK",), ("ICIC",), ("icicibank.com",)),
    BankIdentity("IDBI Bank", "Private Sector Bank", ("IDBI BANK",), ("IBKL",), ("idbibank.in",)),
    BankIdentity("IDFC FIRST Bank", "Private Sector Bank", ("IDFC FIRST BANK", "IDFC BANK"), ("IDFB",), ("idfcfirstbank.com",)),
    BankIdentity("IndusInd Bank", "Private Sector Bank", ("INDUSIND BANK",), ("INDB",), ("indusind.com",)),
    BankIdentity("Jammu & Kashmir Bank", "Private Sector Bank", ("JAMMU & KASHMIR BANK", "JAMMU AND KASHMIR BANK", "J&K BANK"), ("JAKA",), ("jkb.bank.in", "jkbank.com")),
    BankIdentity("Karnataka Bank", "Private Sector Bank", ("KARNATAKA BANK",), ("KARB",), ("karnatakabank.com",)),
    BankIdentity("Karur Vysya Bank", "Private Sector Bank", ("KARUR VYSYA BANK", "KVB"), ("KVBL",), ("kvb.co.in",)),
    BankIdentity("Kotak Mahindra Bank", "Private Sector Bank", ("KOTAK MAHINDRA BANK", "KOTAK BANK"), ("KKBK",), ("kotak.com",)),
    BankIdentity("Nainital Bank", "Private Sector Bank", ("NAINITAL BANK",), ("NTBL",), ("nainitalbank.co.in",)),
    BankIdentity("RBL Bank", "Private Sector Bank", ("RBL BANK", "RATNAKAR BANK"), ("RATN",), ("rblbank.com",)),
    BankIdentity("South Indian Bank", "Private Sector Bank", ("SOUTH INDIAN BANK",), ("SIBL",), ("southindianbank.com",)),
    BankIdentity("Tamilnad Mercantile Bank", "Private Sector Bank", ("TAMILNAD MERCANTILE BANK", "TMB"), ("TMBL",), ("tmb.in",)),
    BankIdentity("YES Bank", "Private Sector Bank", ("YES BANK",), ("YESB",), ("yesbank.in",)),

    # Small finance banks
    BankIdentity("AU Small Finance Bank", "Small Finance Bank", ("AU SMALL FINANCE BANK",), ("AUBL",), ("aubank.in",)),
    BankIdentity("Capital Small Finance Bank", "Small Finance Bank", ("CAPITAL SMALL FINANCE BANK",), ("CLBL",), ("capitalbank.co.in",)),
    BankIdentity("Equitas Small Finance Bank", "Small Finance Bank", ("EQUITAS SMALL FINANCE BANK",), ("ESFB",), ("equitasbank.com",)),
    BankIdentity("ESAF Small Finance Bank", "Small Finance Bank", ("ESAF SMALL FINANCE BANK",), ("ESMF",), ("esafbank.com",)),
    BankIdentity("Jana Small Finance Bank", "Small Finance Bank", ("JANA SMALL FINANCE BANK",), ("JSFB",), ("janabank.com",)),
    BankIdentity("Shivalik Small Finance Bank", "Small Finance Bank", ("SHIVALIK SMALL FINANCE BANK",), ("SMCB",), ("shivalikbank.com",)),
    BankIdentity("slice Small Finance Bank", "Small Finance Bank", ("SLICE SMALL FINANCE BANK", "NORTH EAST SMALL FINANCE BANK"), ("NESF",), ("slice.bank",)),
    BankIdentity("Suryoday Small Finance Bank", "Small Finance Bank", ("SURYODAY SMALL FINANCE BANK",), ("SURY",), ("suryodaybank.com",)),
    BankIdentity("Ujjivan Small Finance Bank", "Small Finance Bank", ("UJJIVAN SMALL FINANCE BANK",), ("UJVN",), ("ujjivansfb.in",)),
    BankIdentity("Unity Small Finance Bank", "Small Finance Bank", ("UNITY SMALL FINANCE BANK",), ("UNSF",), ("theunitybank.com",)),
    BankIdentity("Utkarsh Small Finance Bank", "Small Finance Bank", ("UTKARSH SMALL FINANCE BANK",), ("UTKS",), ("utkarsh.bank",)),

    # Payments banks
    BankIdentity("Airtel Payments Bank", "Payments Bank", ("AIRTEL PAYMENTS BANK",), ("AIRP",), ("airtel.in/bank",)),
    BankIdentity("Fino Payments Bank", "Payments Bank", ("FINO PAYMENTS BANK",), ("FINO",), ("finobank.com",)),
    BankIdentity("India Post Payments Bank", "Payments Bank", ("INDIA POST PAYMENTS BANK", "IPPB"), ("IPOS",), ("ippbonline.com",)),
    BankIdentity("Jio Payments Bank", "Payments Bank", ("JIO PAYMENTS BANK",), ("JIOP",), ("jiopaymentsbank.com",)),
    BankIdentity("NSDL Payments Bank", "Payments Bank", ("NSDL PAYMENTS BANK",), ("NSPB",), ("nsdlpayments.bank",)),
    BankIdentity("Paytm Payments Bank", "Payments Bank", ("PAYTM PAYMENTS BANK",), ("PYTM",), ("paytmbank.com",)),

    # Regional Rural Banks after One-State-One-RRB (2025)
    BankIdentity("Andhra Pradesh Grameena Bank", "Regional Rural Bank", ("ANDHRA PRADESH GRAMEENA BANK", "ANDHRA PRAGATHI GRAMEENA BANK", "CHAITANYA GODAVARI GRAMEENA BANK", "SAPTAGIRI GRAMEENA BANK"), ("UBIN0CG",), ("corebanking.apgb.in", "apgb.in")),
    BankIdentity("Arunachal Pradesh Rural Bank", "Regional Rural Bank", ("ARUNACHAL PRADESH RURAL BANK",), (), ("apruralbank.com",)),
    BankIdentity("Assam Gramin Bank", "Regional Rural Bank", ("ASSAM GRAMIN BANK", "ASSAM GRAMIN VIKASH BANK"), (), ("agvbank.co.in",)),
    BankIdentity("Bihar Gramin Bank", "Regional Rural Bank", ("BIHAR GRAMIN BANK", "DAKSHIN BIHAR GRAMIN BANK", "UTTAR BIHAR GRAMIN BANK"), (), ("dbgb.in", "ubgb.in")),
    BankIdentity("Chhattisgarh Gramin Bank", "Regional Rural Bank", ("CHHATTISGARH GRAMIN BANK", "CHHATTISGARH RAJYA GRAMIN BANK"), (), ("cgbank.in",)),
    BankIdentity("Gujarat Gramin Bank", "Regional Rural Bank", ("GUJARAT GRAMIN BANK", "BARODA GUJARAT GRAMIN BANK", "SAURASHTRA GRAMIN BANK"), (), ("bggb.in", "sgbrrb.org")),
    BankIdentity("Haryana Gramin Bank", "Regional Rural Bank", ("HARYANA GRAMIN BANK", "SARVA HARYANA GRAMIN BANK"), (), ("shgb.co.in",)),
    BankIdentity("Himachal Pradesh Gramin Bank", "Regional Rural Bank", ("HIMACHAL PRADESH GRAMIN BANK",), (), ("hpgb.in",)),
    BankIdentity("Jammu and Kashmir Grameen Bank", "Regional Rural Bank", ("JAMMU AND KASHMIR GRAMEEN BANK", "J&K GRAMEEN BANK", "ELLAQUAI DEHATI BANK"), (), ("jkgb.in",)),
    BankIdentity("Jharkhand Rajya Gramin Bank", "Regional Rural Bank", ("JHARKHAND RAJYA GRAMIN BANK",), (), ("jrgb.in",)),
    BankIdentity("Karnataka Grameena Bank", "Regional Rural Bank", ("KARNATAKA GRAMEENA BANK", "KARNATAKA GRAMIN BANK", "KARNATAKA VIKAS GRAMEENA BANK"), (), ("karnatakagraminbank.com", "kvgbank.com")),
    BankIdentity("Kerala Gramin Bank", "Regional Rural Bank", ("KERALA GRAMIN BANK",), (), ("keralagbank.com",)),
    BankIdentity("Madhya Pradesh Gramin Bank", "Regional Rural Bank", ("MADHYA PRADESH GRAMIN BANK", "MADHYANCHAL GRAMIN BANK"), (), ("mpgb.co.in",)),
    BankIdentity("Maharashtra Gramin Bank", "Regional Rural Bank", ("MAHARASHTRA GRAMIN BANK", "VIDHARBHA KONKAN GRAMIN BANK"), (), ("mahagramin.in", "vkgb.co.in")),
    BankIdentity("Manipur Rural Bank", "Regional Rural Bank", ("MANIPUR RURAL BANK",), (), ("manipurruralbank.com",)),
    BankIdentity("Meghalaya Rural Bank", "Regional Rural Bank", ("MEGHALAYA RURAL BANK",), (), ("meghalayaruralbank.co.in",)),
    BankIdentity("Mizoram Rural Bank", "Regional Rural Bank", ("MIZORAM RURAL BANK",), (), ("mizoramruralbank.in",)),
    BankIdentity("Nagaland Rural Bank", "Regional Rural Bank", ("NAGALAND RURAL BANK",), (), ("nagalandruralbank.com",)),
    BankIdentity("Odisha Grameen Bank", "Regional Rural Bank", ("ODISHA GRAMEEN BANK", "ODISHA GRAMYA BANK", "UTKAL GRAMEEN BANK"), (), ("odishabank.in", "odishagramyabank.in")),
    BankIdentity("Puducherry Grama Bank", "Regional Rural Bank", ("PUDUCHERRY GRAMA BANK", "PUDUVAI BHARATHIAR GRAMA BANK"), (), ("puduvaibharathiargramabank.in",)),
    BankIdentity("Punjab Gramin Bank", "Regional Rural Bank", ("PUNJAB GRAMIN BANK",), (), ("pgb.org.in",)),
    BankIdentity("Rajasthan Gramin Bank", "Regional Rural Bank", ("RAJASTHAN GRAMIN BANK", "RAJASTHAN MARUDHARA GRAMIN BANK", "BARODA RAJASTHAN KSHETRIYA GRAMIN BANK"), (), ("rmgb.in", "brkgb.com")),
    BankIdentity("Tamil Nadu Grama Bank", "Regional Rural Bank", ("TAMIL NADU GRAMA BANK",), (), ("tamilnadugramabank.com",)),
    BankIdentity("Telangana Grameena Bank", "Regional Rural Bank", ("TELANGANA GRAMEENA BANK", "ANDHRA PRADESH GRAMEENA VIKAS BANK"), (), ("tgbhyd.in", "apgvbank.in")),
    BankIdentity("Tripura Gramin Bank", "Regional Rural Bank", ("TRIPURA GRAMIN BANK",), (), ("tripuragraminbank.org",)),
    BankIdentity("Uttar Pradesh Gramin Bank", "Regional Rural Bank", ("UTTAR PRADESH GRAMIN BANK", "ARYAVART BANK", "BARODA U.P. BANK", "BARODA UP BANK", "PRATHAMA U.P. GRAMIN BANK"), (), ("upgbank.com", "aryavart-rrb.com", "barodaupbank.in", "prathamaupbank.com")),
    BankIdentity("Uttarakhand Gramin Bank", "Regional Rural Bank", ("UTTARAKHAND GRAMIN BANK",), (), ("uttarakhandgraminbank.com",)),
    BankIdentity("West Bengal Gramin Bank", "Regional Rural Bank", ("WEST BENGAL GRAMIN BANK", "BANGIYA GRAMIN VIKASH BANK", "PASCHIM BANGA GRAMIN BANK", "UTTARBANGA KSHETRIYA GRAMIN BANK"), (), ("bgvb.in", "pbgbank.com", "ubkgb.org")),

    # Foreign banks commonly issuing Indian account statements
    BankIdentity("HSBC India", "Foreign Bank", ("HSBC BANK", "THE HONGKONG AND SHANGHAI BANKING CORPORATION"), ("HSBC",), ("hsbc.co.in",)),
    BankIdentity("Standard Chartered Bank India", "Foreign Bank", ("STANDARD CHARTERED BANK",), ("SCBL",), ("sc.com/in",)),
    BankIdentity("DBS Bank India", "Foreign Bank", ("DBS BANK INDIA", "DBS BANK"), ("DBSS",), ("dbs.com/in",)),
    BankIdentity("Deutsche Bank India", "Foreign Bank", ("DEUTSCHE BANK",), ("DEUT",), ("deutschebank.co.in",)),
    BankIdentity("Citibank India", "Foreign Bank", ("CITIBANK", "CITI BANK"), ("CITI",), ("citibank.co.in",)),
    BankIdentity("Bank of America India", "Foreign Bank", ("BANK OF AMERICA",), ("BOFA",), ("bankofamerica.com",)),
    BankIdentity("Barclays Bank India", "Foreign Bank", ("BARCLAYS BANK",), ("BARC",), ("barclays.in",)),
    BankIdentity("BNP Paribas India", "Foreign Bank", ("BNP PARIBAS",), ("BNPA",), ("apac.bnpparibas",)),
    BankIdentity("MUFG Bank India", "Foreign Bank", ("MUFG BANK", "BANK OF TOKYO-MITSUBISHI UFJ"), ("BOTM",), ("bk.mufg.jp",)),
    BankIdentity("Mizuho Bank India", "Foreign Bank", ("MIZUHO BANK",), ("MHCB",), ("mizuhogroup.com",)),
    BankIdentity("Sumitomo Mitsui Banking Corporation India", "Foreign Bank", ("SUMITOMO MITSUI BANKING CORPORATION", "SMBC"), ("SMBC",), ("smbc.co.jp",)),
)


def _normalise(value: str) -> str:
    return re.sub(r"[^A-Z0-9.&]+", " ", str(value or "").upper()).strip()


def _alias_present(text_upper: str, alias: str) -> bool:
    normalised_alias = _normalise(alias)
    if not normalised_alias:
        return False
    return re.search(rf"(?<![A-Z0-9]){re.escape(normalised_alias)}(?![A-Z0-9])", text_upper) is not None


def detect_bank_identity(
    text: str,
    *,
    ifsc: str = "",
    source_file: str = "",
) -> BankIdentityMatch | None:
    raw_text = str(text or "")
    text_upper = _normalise(raw_text)
    # Identity headings and account metadata are normally in the first page.
    # Restrict strong name evidence to the beginning so counterparty bank names
    # inside hundreds of transaction narrations cannot relabel the statement.
    header_upper = _normalise(raw_text[:800])
    ifsc_upper = re.sub(r"[^A-Z0-9]", "", str(ifsc or "").upper())
    file_upper = _normalise(Path(source_file or "").stem)

    best: BankIdentityMatch | None = None
    for bank in BANK_IDENTITIES:
        score = 0.0
        evidence: list[str] = []

        matching_prefixes = [
            prefix for prefix in bank.ifsc_prefixes
            if ifsc_upper.startswith(prefix.upper())
        ]
        if matching_prefixes:
            prefix = max(matching_prefixes, key=len)
            # The account IFSC belongs to the issuing bank and therefore outranks
            # bank names appearing merely as transaction counterparties.
            score += 0.96 if len(prefix) > 4 else 0.90
            evidence.append(f"IFSC prefix {prefix}")

        for domain in bank.domains:
            if domain.lower() in raw_text.lower():
                score += 0.92
                evidence.append(f"domain {domain}")
                break

        header_alias_hits = [alias for alias in bank.aliases if _alias_present(header_upper, alias)]
        if header_alias_hits:
            longest = max(header_alias_hits, key=len)
            score += 0.88 if len(_normalise(longest)) >= 12 else 0.55
            evidence.append(f"header name {longest}")
        else:
            # Full-document matches are weak because narrations commonly mention
            # beneficiary and remitter banks. They may support another signal but
            # cannot identify the issuer by themselves.
            body_hits = [alias for alias in bank.aliases if _alias_present(text_upper, alias)]
            if body_hits:
                score += 0.12
                evidence.append("body mention")

        if any(_alias_present(file_upper, alias) for alias in bank.aliases):
            score += 0.10
            evidence.append("filename")

        confidence = min(score, 0.99)
        if confidence < 0.50:
            continue
        candidate = BankIdentityMatch(
            bank.name, bank.category, confidence, tuple(dict.fromkeys(evidence))
        )
        if best is None or candidate.confidence > best.confidence:
            best = candidate

    return best


def apply_detected_bank_identity(meta, df, text: str):
    meta_ifsc = str(getattr(meta, "ifsc", "") or "")
    if not meta_ifsc:
        # Some PDFs place the IFSC value far from its label in text-reading
        # order.  Recover the first header-area IFSC token directly; limiting
        # the search to the beginning avoids beneficiary-bank IFSC codes.
        header_match = re.search(r"\b[A-Z]{4}0[A-Z0-9]{6}\b", str(text or "")[:2500], re.I)
        if header_match:
            meta_ifsc = header_match.group(0).upper()
            try:
                meta.ifsc = meta_ifsc
            except Exception:
                pass
    match = detect_bank_identity(
        text,
        ifsc=meta_ifsc,
        source_file=str(getattr(meta, "source_file", "") or ""),
    )
    if match is None:
        return meta, df

    # Do not let a reused layout/parser brand the statement as another bank.
    # A high-confidence independent identity always wins.
    if match.confidence >= 0.68:
        meta.bank_name = match.name
        if df is not None and not df.empty:
            df = df.copy()
            df["bank_name"] = match.name
    return meta, df
