from __future__ import annotations

import logging

from .idfc import IDFCFirstParser
from .axis import AxisParser
from .hdfc import HDFCParser
from .icici import ICICIParser
from .hsbc import HSBCParser
from .indian_bank import IndianBankModernParser, IndianBankLegacyParser
from .indusind import IndusIndParser
from .kotak import KotakParser
from .sbi import SBIModernParser, SBIOtherParser
from .central_bank_of_india import CentralBankOfIndiaParser
from .yes_bank import YesBankParser
from .city_union_bank import CityUnionBankParser
from .bank_of_baroda import BankOfBarodaParser
from .rbl_bank import RBLBankParser
from .andhra_pradesh_grameena_bank import AndhraPradeshGrameenaBankParser
from .bank_identity import apply_detected_bank_identity
from .base import extract_text
from .template_engine import parse_with_template

logger = logging.getLogger(__name__)

PARSERS = [
    IDFCFirstParser,
    AxisParser,
    AndhraPradeshGrameenaBankParser,
    CentralBankOfIndiaParser,
    YesBankParser,
    CityUnionBankParser,
    BankOfBarodaParser,
    RBLBankParser,
    HDFCParser,
    ICICIParser,
    HSBCParser,
    IndianBankModernParser,
    IndianBankLegacyParser,
    IndusIndParser,
    KotakParser,
    SBIOtherParser,
    SBIModernParser,
]

BANK_OPTIONS = [
    ("auto", "Auto Detect"),
    ("axis", "Axis Bank"),
    ("andhra_pradesh_grameena_bank", "Andhra Pradesh Grameena Bank"),
    ("central_bank_of_india", "Central Bank of India"),
    ("yes_bank", "YES Bank"),
    ("city_union_bank", "City Union Bank"),
    ("bank_of_baroda", "Bank of Baroda"),
    ("rbl_bank", "RBL Bank"),
    ("hdfc", "HDFC Bank"),
    ("icici", "ICICI Bank"),
    ("hsbc", "HSBC Bank"),
    ("idfc", "IDFC FIRST Bank"),
    ("indian_bank", "Indian Bank"),
    ("indusind", "IndusInd Bank"),
    ("kotak", "Kotak Mahindra Bank"),
    ("sbi", "State Bank of India"),
]

BANK_PARSERS = {
    "axis": [AxisParser],
    "andhra_pradesh_grameena_bank": [AndhraPradeshGrameenaBankParser],
    "central_bank_of_india": [CentralBankOfIndiaParser],
    "yes_bank": [YesBankParser],
    "city_union_bank": [CityUnionBankParser],
    "bank_of_baroda": [BankOfBarodaParser],
    "rbl_bank": [RBLBankParser],
    "hdfc": [HDFCParser],
    "icici": [ICICIParser],
    "hsbc": [HSBCParser],
    "idfc": [IDFCFirstParser],
    "indian_bank": [IndianBankModernParser, IndianBankLegacyParser],
    "indusind": [IndusIndParser],
    "kotak": [KotakParser],
    "sbi": [SBIOtherParser, SBIModernParser],
}


def detect_parser(text):
    scored = sorted(
        ((parser.detect(text), parser) for parser in PARSERS),
        key=lambda item: item[0],
        reverse=True,
    )
    if not scored or scored[0][0] <= 0:
        return None, 0
    return scored[0][1](), scored[0][0]


def _usable_result(result) -> bool:
    if result is None:
        return False
    try:
        _meta, frame = result
        return frame is not None and not frame.empty
    except Exception:
        return False


def _identified(result, text: str):
    if not _usable_result(result):
        return None
    meta, frame = result
    return apply_detected_bank_identity(meta, frame, text)


def _attempt(parser, path, text: str, route: str):
    try:
        result = parser.parse(path, text)
        result = _identified(result, text)
        if result is not None:
            meta, frame = result
            logger.info(
                "Bank analyzer route accepted: route=%s parser=%s rows=%s file=%s",
                route,
                getattr(meta, "parser_name", parser.__class__.__name__),
                len(frame),
                path,
            )
            return result
        logger.warning(
            "Bank analyzer route returned no rows: route=%s parser=%s file=%s",
            route,
            parser.__class__.__name__,
            path,
        )
    except Exception as exc:
        logger.warning(
            "Bank analyzer route failed: route=%s parser=%s file=%s error=%s",
            route,
            parser.__class__.__name__,
            path,
            exc,
            exc_info=True,
        )
    return None


def _template_first(path, text: str, hint: str):
    try:
        result = parse_with_template(path, text=text, bank_hint=hint)
        result = _identified(result, text)
        if result is not None:
            meta, frame = result
            logger.info(
                "Bank analyzer template accepted: parser=%s rows=%s file=%s",
                getattr(meta, "parser_name", "TemplateBasedStatementParser"),
                len(frame),
                path,
            )
            return result
        logger.warning("Bank analyzer template returned no rows: file=%s", path)
    except Exception as exc:
        logger.warning(
            "Bank analyzer template rejected: file=%s error=%s",
            path,
            exc,
            exc_info=True,
        )
    return None


def _ordered_bank_candidates(text: str):
    scored = sorted(
        ((parser.detect(text), parser) for parser in PARSERS),
        key=lambda item: item[0],
        reverse=True,
    )
    return [parser for score, parser in scored if score > 0]


def parse_pdf(path, bank_hint: str | None = None):
    """Parse a statement using a validated template first, then bank parsers.

    Bank identity is resolved independently after extraction, so using a shared
    layout never labels a statement as the bank whose parser happens to have a
    similar table structure.
    """
    hint = (bank_hint or "auto").strip().lower()
    text = extract_text(path)

    template_result = _template_first(path, text, hint)
    if template_result is not None:
        return template_result

    if hint and hint != "auto":
        candidates = BANK_PARSERS.get(hint, [])
        for parser_class in sorted(candidates, key=lambda cls: cls.detect(text), reverse=True):
            result = _attempt(parser_class(), path, text, f"selected:{hint}")
            if result is not None:
                return result
        label = dict(BANK_OPTIONS).get(hint, "the selected bank")
        raise ValueError(
            f"The uploaded statement could not be parsed by a validated layout template or the "
            f"selected {label} parser. Please verify that the PDF is readable and supported."
        )

    attempted: set[type] = set()
    for parser_class in _ordered_bank_candidates(text):
        attempted.add(parser_class)
        result = _attempt(parser_class(), path, text, "auto-detected")
        if result is not None:
            return result

    if not (text or "").strip():
        result = _attempt(HSBCParser(), path, text, "image-only-ocr")
        if result is not None:
            return result

    # A detector may miss a new bank layout even though an existing dedicated
    # parser can still extract and reconcile it. Keep this compatibility pass.
    for parser_class in PARSERS:
        if parser_class in attempted:
            continue
        result = _attempt(parser_class(), path, text, "compatibility-fallback")
        if result is not None:
            return result

    raise ValueError(
        "The bank statement format was identified, but no transaction rows could be extracted. "
        "The validated template engine and all bank-specific parsers were attempted. "
        "Please verify that the PDF is text-readable and that this statement layout is supported."
    )
