"""
Swedish Payment Line Parser

Handles parsing and validation of Swedish machine-readable payment lines.
Unifies payment line parsing logic that was previously duplicated across multiple modules.

Standard Swedish payment line format:
    # <OCR> # <Kronor> <Öre> <Type> > <Account>#<Check>#

Example:
    # 94228110015950070 # 15658 00 8 > 48666036#14#

This parser handles common OCR errors:
- Spaces in numbers: "12 0 0" → "1200"
- Missing symbols: Missing ">"
- Spaces in check digits: "#41 #" → "#41#"
"""

import re
import logging
from dataclasses import dataclass
from typing import Optional

from shared.exceptions import PaymentLineParseError


@dataclass
class PaymentLineData:
    """Parsed payment line data."""

    ocr_number: str
    """OCR reference number (payment reference)"""

    amount: Optional[str] = None
    """Amount in format KRONOR.ÖRE (e.g., '1200.00'), None if not present"""

    account_number: Optional[str] = None
    """Bankgiro or Plusgiro account number"""

    record_type: Optional[str] = None
    """Record type digit (usually '5' or '8' or '9')"""

    check_digits: Optional[str] = None
    """Check digits for account validation"""

    raw_text: str = ""
    """Original raw text that was parsed"""

    is_valid: bool = True
    """Whether parsing was successful"""

    error: Optional[str] = None
    """Error message if parsing failed"""

    parse_method: str = "unknown"
    """Which parsing pattern was used (for debugging)"""


class PaymentLineParser:
    """Parser for Swedish payment lines with OCR error handling."""

    # Pattern with amount: # OCR # KRONOR ÖRE TYPE > ACCOUNT#CHECK#
    FULL_PATTERN = re.compile(
        r'#\s*(\d[\d\s]*)\s*#\s*([\d\s]+?)\s+(\d{2})\s+(\d)\s*>?\s*([\d\s]+)\s*#\s*(\d+)\s*#'
    )

    # Pattern without amount: # OCR # > ACCOUNT#CHECK#
    NO_AMOUNT_PATTERN = re.compile(
        r'#\s*(\d[\d\s]*)\s*#\s*>?\s*([\d\s]+)\s*#\s*(\d+)\s*#'
    )

    # Alternative pattern: look for OCR > ACCOUNT# pattern
    ALT_PATTERN = re.compile(
        r'(\d[\d\s]{10,})\s*#[^>]*>?\s*([\d\s]+)\s*#\s*(\d+)\s*#'
    )

    # Account only pattern: > ACCOUNT#CHECK#
    ACCOUNT_ONLY_PATTERN = re.compile(
        r'>\s*([\d\s]+)\s*#\s*(\d+)\s*#'
    )

    def __init__(self):
        """Initialize parser with logger."""
        self.logger = logging.getLogger(__name__)

    def parse(self, text: str) -> PaymentLineData:
        """
        Parse payment line text.

        Handles common OCR errors:
        - Spaces in numbers: "12 0 0" → "1200"
        - Missing symbols: Missing ">"
        - Spaces in check digits: "#41 #" → "#41#"

        Args:
            text: Raw payment line text from OCR

        Returns:
            PaymentLineData with parsed fields or error information
        """
        if not text or not text.strip():
            return PaymentLineData(
                ocr_number="",
                raw_text=text,
                is_valid=False,
                error="Empty payment line text",
                parse_method="none"
            )

        text = text.strip()

        # Try full pattern with amount
        match = self.FULL_PATTERN.search(text)
        if match:
            return self._parse_full_match(match, text)

        # Try pattern without amount
        match = self.NO_AMOUNT_PATTERN.search(text)
        if match:
            return self._parse_no_amount_match(match, text)

        # Try alternative pattern
        match = self.ALT_PATTERN.search(text)
        if match:
            return self._parse_alt_match(match, text)

        # Try account only pattern
        match = self.ACCOUNT_ONLY_PATTERN.search(text)
        if match:
            return self._parse_account_only_match(match, text)

        # No match - return error
        return PaymentLineData(
            ocr_number="",
            raw_text=text,
            is_valid=False,
            error="No valid payment line format found",
            parse_method="none"
        )

    def _parse_full_match(self, match: re.Match, raw_text: str) -> PaymentLineData:
        """Parse full pattern match (with amount)."""
        ocr = self._clean_digits(match.group(1))
        kronor = self._clean_digits(match.group(2))
        ore = match.group(3)
        record_type = match.group(4)
        account = self._clean_digits(match.group(5))
        check_digits = match.group(6)

        amount = f"{kronor}.{ore}"

        return PaymentLineData(
            ocr_number=ocr,
            amount=amount,
            account_number=account,
            record_type=record_type,
            check_digits=check_digits,
            raw_text=raw_text,
            is_valid=True,
            error=None,
            parse_method="full"
        )

    def _parse_no_amount_match(self, match: re.Match, raw_text: str) -> PaymentLineData:
        """Parse pattern match without amount."""
        ocr = self._clean_digits(match.group(1))
        account = self._clean_digits(match.group(2))
        check_digits = match.group(3)

        return PaymentLineData(
            ocr_number=ocr,
            amount=None,
            account_number=account,
            record_type=None,
            check_digits=check_digits,
            raw_text=raw_text,
            is_valid=True,
            error=None,
            parse_method="no_amount"
        )

    def _parse_alt_match(self, match: re.Match, raw_text: str) -> PaymentLineData:
        """Parse alternative pattern match."""
        ocr = self._clean_digits(match.group(1))
        account = self._clean_digits(match.group(2))
        check_digits = match.group(3)

        return PaymentLineData(
            ocr_number=ocr,
            amount=None,
            account_number=account,
            record_type=None,
            check_digits=check_digits,
            raw_text=raw_text,
            is_valid=True,
            error=None,
            parse_method="alternative"
        )

    def _parse_account_only_match(self, match: re.Match, raw_text: str) -> PaymentLineData:
        """Parse account-only pattern match."""
        account = self._clean_digits(match.group(1))
        check_digits = match.group(2)

        return PaymentLineData(
            ocr_number="",
            amount=None,
            account_number=account,
            record_type=None,
            check_digits=check_digits,
            raw_text=raw_text,
            is_valid=True,
            error="Partial payment line (account only)",
            parse_method="account_only"
        )

    def _clean_digits(self, text: str) -> str:
        """Remove spaces from digit string (OCR error correction)."""
        return text.replace(' ', '')

    def format_machine_readable(self, data: PaymentLineData) -> str:
        """
        Format parsed data back to machine-readable format.

        Returns:
            Formatted string in standard Swedish payment line format
        """
        if not data.is_valid:
            return data.raw_text

        # Full format with amount
        if data.amount and data.record_type:
            kronor, ore = data.amount.split('.')
            return (
                f"# {data.ocr_number} # {kronor} {ore} {data.record_type} > "
                f"{data.account_number}#{data.check_digits}#"
            )

        # Format without amount
        if data.ocr_number and data.account_number:
            return f"# {data.ocr_number} # > {data.account_number}#{data.check_digits}#"

        # Account only
        if data.account_number:
            return f"> {data.account_number}#{data.check_digits}#"

        # Fallback
        return data.raw_text

    def format_for_field_extractor(self, data: PaymentLineData) -> tuple[Optional[str], bool, Optional[str]]:
        """
        Format parsed data for FieldExtractor compatibility.

        Returns:
            Tuple of (formatted_text, is_valid, error_message) matching FieldExtractor's API
        """
        if not data.is_valid:
            return None, False, data.error

        formatted = self.format_machine_readable(data)
        return formatted, True, data.error
