"""
Shared document processing logic for autolabel.

This module provides the core processing functions used by both
single document mode and batch processing mode to ensure consistent
matching and annotation logic.
"""

from __future__ import annotations

from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Tuple

from training.data.autolabel_report import FieldMatchResult
from shared.matcher import FieldMatcher
from shared.normalize import normalize_field
from shared.ocr.machine_code_parser import MachineCodeParser
from shared.fields import TRAINING_FIELD_CLASSES as FIELD_CLASSES
from training.yolo.annotation_generator import AnnotationGenerator


def match_supplier_accounts(
    tokens: list,
    supplier_accounts_value: str,
    matcher: FieldMatcher,
    page_no: int,
    matches: Dict[str, list],
    matched_fields: Set[str],
    report: Any,
) -> None:
    """
    Match supplier_accounts field and map to Bankgiro/Plusgiro.

    This logic is shared between single document mode and batch mode
    to ensure consistent BG/PG type detection.

    Args:
        tokens: List of text tokens from the page
        supplier_accounts_value: Raw value from CSV (e.g., "BG:xxx | PG:yyy")
        matcher: FieldMatcher instance
        page_no: Current page number
        matches: Dictionary to store matched fields (modified in place)
        matched_fields: Set of matched field names (modified in place)
        report: AutoLabelReport instance
    """
    if not supplier_accounts_value:
        return

    # Parse accounts: "BG:xxx | PG:yyy" format
    accounts = [acc.strip() for acc in str(supplier_accounts_value).split('|')]

    for account in accounts:
        account = account.strip()
        if not account:
            continue

        # Determine account type (BG or PG) and extract account number
        account_type = None
        account_number = account  # Default to full value

        if account.upper().startswith('BG:'):
            account_type = 'Bankgiro'
            account_number = account[3:].strip()  # Remove "BG:" prefix
        elif account.upper().startswith('BG '):
            account_type = 'Bankgiro'
            account_number = account[2:].strip()  # Remove "BG" prefix
        elif account.upper().startswith('PG:'):
            account_type = 'Plusgiro'
            account_number = account[3:].strip()  # Remove "PG:" prefix
        elif account.upper().startswith('PG '):
            account_type = 'Plusgiro'
            account_number = account[2:].strip()  # Remove "PG" prefix
        else:
            # Try to guess from format - Plusgiro often has format XXXXXXX-X
            digits = ''.join(c for c in account if c.isdigit())
            if len(digits) == 8 and '-' in account:
                account_type = 'Plusgiro'
            elif len(digits) in (7, 8):
                account_type = 'Bankgiro'  # Default to Bankgiro

        if not account_type:
            continue

        # Normalize and match using the account number (without prefix)
        normalized = normalize_field('supplier_accounts', account_number)
        field_matches = matcher.find_matches(tokens, account_type, normalized, page_no)

        if field_matches:
            best = field_matches[0]
            # Add to matches under the target class (Bankgiro/Plusgiro)
            if account_type not in matches:
                matches[account_type] = []
            matches[account_type].extend(field_matches)
            matched_fields.add('supplier_accounts')

            report.add_field_result(FieldMatchResult(
                field_name=f'supplier_accounts({account_type})',
                csv_value=account_number,  # Store without prefix
                matched=True,
                score=best.score,
                matched_text=best.matched_text,
                candidate_used=best.value,
                bbox=best.bbox,
                page_no=page_no,
                context_keywords=best.context_keywords
            ))


def detect_payment_line(
    tokens: list,
    page_height: float,
    page_width: float,
) -> Optional[Any]:
    """
    Detect payment line (machine code) and return the parsed result.

    This function only detects and parses the payment line, without generating
    annotations. The caller can use the result to extract amount for cross-validation.

    Args:
        tokens: List of text tokens from the page
        page_height: Page height in PDF points
        page_width: Page width in PDF points

    Returns:
        MachineCodeResult if standard format detected (confidence >= 0.95), None otherwise
    """
    # Use 55% of page height as bottom region to catch payment lines
    # that may be in the middle of the page (e.g., payment slips)
    mc_parser = MachineCodeParser(bottom_region_ratio=0.55)
    mc_result = mc_parser.parse(tokens, page_height, page_width)

    # Only return if we found a STANDARD payment line format
    # (confidence 0.95 means standard pattern matched with # and > symbols)
    is_standard_format = mc_result.confidence >= 0.95
    if is_standard_format:
        return mc_result
    return None


def match_payment_line(
    tokens: list,
    page_height: float,
    page_width: float,
    min_confidence: float,
    generator: AnnotationGenerator,
    annotations: list,
    img_width: int,
    img_height: int,
    dpi: int,
    matched_fields: Set[str],
    report: Any,
    page_no: int,
    mc_result: Optional[Any] = None,
) -> None:
    """
    Annotate payment line (machine code) using pre-detected result.

    This logic is shared between single document mode and batch mode
    to ensure consistent payment_line detection.

    Args:
        tokens: List of text tokens from the page
        page_height: Page height in PDF points
        page_width: Page width in PDF points
        min_confidence: Minimum confidence threshold
        generator: AnnotationGenerator instance
        annotations: List of annotations (modified in place)
        img_width: Image width in pixels
        img_height: Image height in pixels
        dpi: DPI used for rendering
        matched_fields: Set of matched field names (modified in place)
        report: AutoLabelReport instance
        page_no: Current page number
        mc_result: Pre-detected MachineCodeResult (from detect_payment_line)
    """
    # Use pre-detected result if provided, otherwise detect now
    if mc_result is None:
        mc_result = detect_payment_line(tokens, page_height, page_width)

    # Only add payment_line if we have a valid standard format result
    if mc_result is None:
        return

    if mc_result.confidence >= min_confidence:
        region_bbox = mc_result.get_region_bbox()
        if region_bbox:
            generator.add_payment_line_annotation(
                annotations, region_bbox, mc_result.confidence,
                img_width, img_height, dpi=dpi
            )
            # Store payment_line result in database
            matched_fields.add('payment_line')
            report.add_field_result(FieldMatchResult(
                field_name='payment_line',
                csv_value=mc_result.raw_line[:200] if mc_result.raw_line else '',
                matched=True,
                score=mc_result.confidence,
                matched_text=f"OCR:{mc_result.ocr or ''} Amount:{mc_result.amount or ''} BG:{mc_result.bankgiro or ''}",
                candidate_used='machine_code_parser',
                bbox=region_bbox,
                page_no=page_no,
                context_keywords=['payment_line', 'machine_code']
            ))


def match_standard_fields(
    tokens: list,
    row_dict: Dict[str, Any],
    matcher: FieldMatcher,
    page_no: int,
    matches: Dict[str, list],
    matched_fields: Set[str],
    report: Any,
    payment_line_amount: Optional[str] = None,
    payment_line_bbox: Optional[tuple] = None,
) -> None:
    """
    Match standard fields from CSV to tokens.

    This excludes payment_line (detected separately) and supplier_accounts
    (handled by match_supplier_accounts).

    Args:
        tokens: List of text tokens from the page
        row_dict: Dictionary of field values from CSV
        matcher: FieldMatcher instance
        page_no: Current page number
        matches: Dictionary to store matched fields (modified in place)
        matched_fields: Set of matched field names (modified in place)
        report: AutoLabelReport instance
        payment_line_amount: Amount extracted from payment_line (takes priority over CSV)
        payment_line_bbox: Bounding box of payment_line region (used as fallback for Amount)
    """
    for field_name in FIELD_CLASSES.keys():
        # Skip fields handled separately
        if field_name == 'payment_line':
            continue
        if field_name in ('Bankgiro', 'Plusgiro'):
            continue  # Handled via supplier_accounts

        value = row_dict.get(field_name)

        # For Amount field: only use payment_line amount if it matches CSV value
        use_payment_line_amount = False
        if field_name == 'Amount' and payment_line_amount and value:
            # Parse both amounts and check if they're close
            try:
                csv_amt = float(str(value).replace(',', '.').replace(' ', ''))
                pl_amt = float(str(payment_line_amount).replace(',', '.').replace(' ', ''))
                if abs(csv_amt - pl_amt) < 0.01:
                    # Payment line amount matches CSV, use it for better bbox
                    value = payment_line_amount
                    use_payment_line_amount = True
                # Otherwise keep CSV value for matching
            except (ValueError, TypeError):
                pass

        if not value:
            continue

        normalized = normalize_field(field_name, str(value))
        field_matches = matcher.find_matches(tokens, field_name, normalized, page_no)

        if field_matches:
            best = field_matches[0]
            matches[field_name] = field_matches
            matched_fields.add(field_name)

            # For Amount: note if we used payment_line amount
            csv_value_display = str(row_dict.get(field_name, value))
            if field_name == 'Amount' and use_payment_line_amount:
                csv_value_display = f"{row_dict.get(field_name)} (matched via payment_line: {payment_line_amount})"

            report.add_field_result(FieldMatchResult(
                field_name=field_name,
                csv_value=csv_value_display,
                matched=True,
                score=best.score,
                matched_text=best.matched_text,
                candidate_used=best.value,
                bbox=best.bbox,
                page_no=page_no,
                context_keywords=best.context_keywords
            ))
        elif field_name == 'Amount' and use_payment_line_amount and payment_line_bbox:
            # Fallback: Amount not found via token matching, but payment_line
            # successfully extracted a matching amount. Use payment_line bbox.
            # This handles cases where text PDFs merge multiple values into one token.
            from shared.matcher.field_matcher import Match

            fallback_match = Match(
                field='Amount',
                value=payment_line_amount,
                bbox=payment_line_bbox,
                page_no=page_no,
                score=0.9,
                matched_text=f"Amount:{payment_line_amount}",
                context_keywords=['payment_line', 'amount']
            )
            matches[field_name] = [fallback_match]
            matched_fields.add(field_name)
            csv_value_display = f"{row_dict.get(field_name)} (via payment_line: {payment_line_amount})"

            report.add_field_result(FieldMatchResult(
                field_name=field_name,
                csv_value=csv_value_display,
                matched=True,
                score=0.9,  # High confidence since payment_line parsing succeeded
                matched_text=f"Amount:{payment_line_amount}",
                candidate_used='payment_line_fallback',
                bbox=payment_line_bbox,
                page_no=page_no,
                context_keywords=['payment_line', 'amount']
            ))


def record_unmatched_fields(
    row_dict: Dict[str, Any],
    matched_fields: Set[str],
    report: Any,
) -> None:
    """
    Record fields from CSV that were not matched.

    Args:
        row_dict: Dictionary of field values from CSV
        matched_fields: Set of matched field names
        report: AutoLabelReport instance
    """
    for field_name in FIELD_CLASSES.keys():
        if field_name == 'payment_line':
            continue  # payment_line doesn't come from CSV
        if field_name in ('Bankgiro', 'Plusgiro'):
            continue  # These come from supplier_accounts

        value = row_dict.get(field_name)
        if value and field_name not in matched_fields:
            report.add_field_result(FieldMatchResult(
                field_name=field_name,
                csv_value=str(value),
                matched=False,
                page_no=-1
            ))

    # Check if supplier_accounts was not matched
    if row_dict.get('supplier_accounts') and 'supplier_accounts' not in matched_fields:
        report.add_field_result(FieldMatchResult(
            field_name='supplier_accounts',
            csv_value=str(row_dict.get('supplier_accounts')),
            matched=False,
            page_no=-1
        ))


def process_page(
    tokens: list,
    row_dict: Dict[str, Any],
    page_no: int,
    page_height: float,
    page_width: float,
    img_width: int,
    img_height: int,
    dpi: int,
    min_confidence: float,
    matches: Dict[str, list],
    matched_fields: Set[str],
    report: Any,
    result_stats: Dict[str, int],
) -> Tuple[list, int]:
    """
    Process a single page: match fields and generate annotations.

    This is the main entry point for page processing, used by both
    single document mode and batch mode.

    Processing order:
    1. Detect payment_line first to extract amount
    2. Match standard fields (using payment_line amount if available)
    3. Match supplier_accounts
    4. Generate annotations

    Args:
        tokens: List of text tokens from the page
        row_dict: Dictionary of field values from CSV
        page_no: Current page number
        page_height: Page height in PDF points
        page_width: Page width in PDF points
        img_width: Image width in pixels
        img_height: Image height in pixels
        dpi: DPI used for rendering
        min_confidence: Minimum confidence threshold
        matches: Dictionary to store matched fields (modified in place)
        matched_fields: Set of matched field names (modified in place)
        report: AutoLabelReport instance
        result_stats: Dictionary of annotation stats (modified in place)

    Returns:
        Tuple of (annotations list, annotation count)
    """
    matcher = FieldMatcher()
    generator = AnnotationGenerator(min_confidence=min_confidence)

    # Step 1: Detect payment_line FIRST to extract amount
    # This allows us to use the payment_line amount for matching Amount field
    mc_result = detect_payment_line(tokens, page_height, page_width)

    # Extract amount and bbox from payment_line if available
    payment_line_amount = None
    payment_line_bbox = None
    if mc_result and mc_result.amount:
        payment_line_amount = mc_result.amount
        payment_line_bbox = mc_result.get_region_bbox()

    # Step 2: Match standard fields (using payment_line amount if available)
    match_standard_fields(
        tokens, row_dict, matcher, page_no,
        matches, matched_fields, report,
        payment_line_amount=payment_line_amount,
        payment_line_bbox=payment_line_bbox
    )

    # Step 3: Match supplier_accounts -> Bankgiro/Plusgiro
    supplier_accounts_value = row_dict.get('supplier_accounts')
    if supplier_accounts_value:
        match_supplier_accounts(
            tokens, supplier_accounts_value, matcher, page_no,
            matches, matched_fields, report
        )

    # Generate annotations from matches
    annotations = generator.generate_from_matches(
        matches, img_width, img_height, dpi=dpi
    )

    # Step 4: Add payment_line annotation (reuse the pre-detected result)
    match_payment_line(
        tokens, page_height, page_width, min_confidence,
        generator, annotations, img_width, img_height, dpi,
        matched_fields, report, page_no,
        mc_result=mc_result
    )

    # Update stats
    for ann in annotations:
        class_name = list(FIELD_CLASSES.keys())[ann.class_id]
        result_stats[class_name] += 1

    return annotations, len(annotations)
