"""
Merged Cell Handler

Handles detection and extraction of data from tables with merged cells,
a common issue with PP-StructureV3 OCR output.
"""

import re
import logging
from typing import TYPE_CHECKING

from .models import LineItem

if TYPE_CHECKING:
    from .html_table_parser import ColumnMapper

logger = logging.getLogger(__name__)

# Minimum positive amount to consider as line item (filters noise like row indices)
MIN_AMOUNT_THRESHOLD = 100


class MergedCellHandler:
    """Handles tables with vertically merged cells from PP-StructureV3."""

    def __init__(self, mapper: "ColumnMapper"):
        """
        Initialize handler.

        Args:
            mapper: ColumnMapper instance for header keyword detection.
        """
        self.mapper = mapper

    def has_vertically_merged_cells(self, rows: list[list[str]]) -> bool:
        """
        Check if table rows contain vertically merged data in single cells.

        PP-StructureV3 sometimes merges multiple table rows into single cells, e.g.:
        ["Produktnr 1457280 1457280 1060381", "", "Antal 6ST 6ST 1ST", "Pris 127,20 127,20 159,20"]

        Detection: cells contain repeating patterns of numbers or keywords suggesting multiple lines.
        """
        if not rows:
            return False

        for row in rows:
            for cell in row:
                if not cell or len(cell) < 20:
                    continue

                # Check for multiple product numbers (7+ digit patterns)
                product_nums = re.findall(r"\b\d{7}\b", cell)
                if len(product_nums) >= 2:
                    logger.debug(f"has_vertically_merged_cells: found {len(product_nums)} product numbers in cell")
                    return True

                # Check for multiple prices (Swedish format: 123,45 or 1 234,56)
                prices = re.findall(r"\b\d{1,3}(?:\s?\d{3})*[,\.]\d{2}\b", cell)
                if len(prices) >= 3:
                    logger.debug(f"has_vertically_merged_cells: found {len(prices)} prices in cell")
                    return True

                # Check for multiple quantity patterns (e.g., "6ST 6ST 1ST")
                quantities = re.findall(r"\b\d+\s*(?:ST|st|PCS|pcs)\b", cell)
                if len(quantities) >= 2:
                    logger.debug(f"has_vertically_merged_cells: found {len(quantities)} quantities in cell")
                    return True

        return False

    def split_merged_rows(
        self, rows: list[list[str]]
    ) -> tuple[list[str], list[list[str]]]:
        """
        Split vertically merged cells back into separate rows.

        Handles complex cases where PP-StructureV3 merges content across
        multiple HTML rows. For example, 5 line items might be spread across
        3 HTML rows with content mixed together.

        Strategy:
        1. Merge all row content per column
        2. Detect how many actual data rows exist (by counting product numbers)
        3. Split each column's content into that many lines

        Returns header and data rows.
        """
        if not rows:
            return [], []

        # Filter out completely empty rows
        non_empty_rows = [r for r in rows if any(cell.strip() for cell in r)]
        if not non_empty_rows:
            return [], rows

        # Determine column count
        col_count = max(len(r) for r in non_empty_rows)

        # Merge content from all rows for each column
        merged_columns = []
        for col_idx in range(col_count):
            col_content = []
            for row in non_empty_rows:
                if col_idx < len(row) and row[col_idx].strip():
                    col_content.append(row[col_idx].strip())
            merged_columns.append(" ".join(col_content))

        logger.debug(f"split_merged_rows: merged columns = {merged_columns}")

        # Count how many actual data rows we should have
        # Use the column with most product numbers as reference
        expected_rows = self._count_expected_rows(merged_columns)
        logger.debug(f"split_merged_rows: expecting {expected_rows} data rows")

        if expected_rows <= 1:
            # Not enough data for splitting
            return [], rows

        # Split each column based on expected row count
        split_columns = []
        for col_idx, col_text in enumerate(merged_columns):
            if not col_text.strip():
                split_columns.append([""] * (expected_rows + 1))  # +1 for header
                continue
            lines = self._split_cell_content_for_rows(col_text, expected_rows)
            split_columns.append(lines)

        # Ensure all columns have same number of lines (immutable approach)
        max_lines = max(len(col) for col in split_columns)
        split_columns = [
            col + [""] * (max_lines - len(col))
            for col in split_columns
        ]

        logger.debug(f"split_merged_rows: split into {max_lines} lines total")

        # First line is header, rest are data rows
        header = [col[0] for col in split_columns]
        data_rows = []
        for line_idx in range(1, max_lines):
            row = [col[line_idx] if line_idx < len(col) else "" for col in split_columns]
            if any(cell.strip() for cell in row):
                data_rows.append(row)

        logger.debug(f"split_merged_rows: header={header}, data_rows count={len(data_rows)}")
        return header, data_rows

    def _count_expected_rows(self, merged_columns: list[str]) -> int:
        """
        Count how many data rows should exist based on content patterns.

        Returns the maximum count found from:
        - Product numbers (7 digits)
        - Quantity patterns (number + ST/PCS)
        - Amount patterns (in columns likely to be totals)
        """
        max_count = 0

        for col_text in merged_columns:
            if not col_text:
                continue

            # Count product numbers (most reliable indicator)
            product_nums = re.findall(r"\b\d{7}\b", col_text)
            max_count = max(max_count, len(product_nums))

            # Count quantities (e.g., "6ST 6ST 1ST 1ST 1ST")
            quantities = re.findall(r"\b\d+\s*(?:ST|st|PCS|pcs)\b", col_text)
            max_count = max(max_count, len(quantities))

        return max_count

    def _split_cell_content_for_rows(self, cell: str, expected_rows: int) -> list[str]:
        """
        Split cell content knowing how many data rows we expect.

        This is smarter than split_cell_content because it knows the target count.
        """
        cell = cell.strip()

        # Try product number split first
        product_pattern = re.compile(r"(\b\d{7}\b)")
        products = product_pattern.findall(cell)
        if len(products) == expected_rows:
            parts = product_pattern.split(cell)
            header = parts[0].strip() if parts else ""
            # Include description text after each product number
            values = []
            for i in range(1, len(parts), 2):  # Odd indices are product numbers
                if i < len(parts):
                    prod_num = parts[i].strip()
                    # Check if there's description text after
                    desc = parts[i + 1].strip() if i + 1 < len(parts) else ""
                    # If description looks like text (not another pattern), include it
                    if desc and not re.match(r"^\d{7}$", desc):
                        # Truncate at next product number pattern if any
                        desc_clean = re.split(r"\d{7}", desc)[0].strip()
                        if desc_clean:
                            values.append(f"{prod_num} {desc_clean}")
                        else:
                            values.append(prod_num)
                    else:
                        values.append(prod_num)
            if len(values) == expected_rows:
                return [header] + values

        # Try quantity split
        qty_pattern = re.compile(r"(\b\d+\s*(?:ST|st|PCS|pcs|M|m|KG|kg)\b)")
        quantities = qty_pattern.findall(cell)
        if len(quantities) == expected_rows:
            parts = qty_pattern.split(cell)
            header = parts[0].strip() if parts else ""
            values = [p.strip() for p in parts[1:] if p.strip() and qty_pattern.match(p)]
            if len(values) == expected_rows:
                return [header] + values

        # Try amount split for discount+totalsumma columns
        cell_lower = cell.lower()
        has_discount = any(kw in cell_lower for kw in ["rabatt", "discount"])
        has_total = any(kw in cell_lower for kw in ["totalsumma", "total", "summa", "belopp"])

        if has_discount and has_total:
            # Extract only amounts (3+ digit numbers), skip discount percentages
            amount_pattern = re.compile(r"\b(\d{3,}[,\.]\d{2})\b")
            amounts = amount_pattern.findall(cell)
            if len(amounts) >= expected_rows:
                # Take the last expected_rows amounts (they are likely the totals)
                return ["Totalsumma"] + amounts[:expected_rows]

        # Try price split
        price_pattern = re.compile(r"(\b\d{1,3}(?:\s?\d{3})*[,\.]\d{2}\b)")
        prices = price_pattern.findall(cell)
        if len(prices) >= expected_rows:
            parts = price_pattern.split(cell)
            header = parts[0].strip() if parts else ""
            values = [p.strip() for p in parts[1:] if p.strip() and price_pattern.match(p)]
            if len(values) >= expected_rows:
                return [header] + values[:expected_rows]

        # Fall back to original single-value behavior
        return [cell]

    def split_cell_content(self, cell: str) -> list[str]:
        """
        Split a cell containing merged multi-line content.

        Strategies:
        1. Look for product number patterns (7 digits)
        2. Look for quantity patterns (number + ST/PCS)
        3. Look for price patterns (with decimal)
        4. Handle interleaved discount+amount patterns
        """
        cell = cell.strip()

        # Strategy 1: Split by product numbers (common pattern: "Produktnr 1234567 1234568")
        product_pattern = re.compile(r"(\b\d{7}\b)")
        products = product_pattern.findall(cell)
        if len(products) >= 2:
            # Extract header (text before first product number) and values
            parts = product_pattern.split(cell)
            header = parts[0].strip() if parts else ""
            values = [p for p in parts[1:] if p.strip() and re.match(r"\d{7}", p)]
            return [header] + values

        # Strategy 2: Split by quantities (e.g., "Antal 6ST 6ST 1ST")
        qty_pattern = re.compile(r"(\b\d+\s*(?:ST|st|PCS|pcs|M|m|KG|kg)\b)")
        quantities = qty_pattern.findall(cell)
        if len(quantities) >= 2:
            parts = qty_pattern.split(cell)
            header = parts[0].strip() if parts else ""
            values = [p.strip() for p in parts[1:] if p.strip() and qty_pattern.match(p)]
            return [header] + values

        # Strategy 3: Handle interleaved discount+amount (e.g., "Rabatt i% Totalsumma 10,0 686,88 10,0 686,88")
        # Check if header contains two keywords indicating merged columns
        cell_lower = cell.lower()
        has_discount_header = any(kw in cell_lower for kw in ["rabatt", "discount"])
        has_amount_header = any(kw in cell_lower for kw in ["totalsumma", "summa", "belopp", "total"])

        if has_discount_header and has_amount_header:
            # Extract all numbers and pair them (discount, amount, discount, amount, ...)
            # Pattern for amounts: 3+ digit numbers with decimals (e.g., 686,88)
            amount_pattern = re.compile(r"\b(\d{3,}[,\.]\d{2})\b")
            amounts = amount_pattern.findall(cell)

            if len(amounts) >= 2:
                # Return header as "Totalsumma" (amount header) so it maps to amount field, not deduction
                # This avoids the "Rabatt" keyword causing is_deduction=True
                header = "Totalsumma"
                return [header] + amounts

        # Strategy 4: Split by prices (e.g., "Pris 127,20 127,20 159,20")
        price_pattern = re.compile(r"(\b\d{1,3}(?:\s?\d{3})*[,\.]\d{2}\b)")
        prices = price_pattern.findall(cell)
        if len(prices) >= 2:
            parts = price_pattern.split(cell)
            header = parts[0].strip() if parts else ""
            values = [p.strip() for p in parts[1:] if p.strip() and price_pattern.match(p)]
            return [header] + values

        # No pattern detected, return as single value
        return [cell]

    def has_merged_header(self, header: list[str] | None) -> bool:
        """
        Check if header appears to be a merged cell containing multiple column names.

        This happens when OCR merges table headers into a single cell, e.g.:
        "Specifikation 0218103-1201 2 rum och kök Hyra Avdrag" instead of separate columns.

        Also handles cases where PP-StructureV3 produces headers like:
        ["Specifikation ... Hyra Avdrag", "", "", ""] with empty trailing cells.
        """
        if header is None or not header:
            return False

        # Filter out empty cells to find the actual content
        non_empty_cells = [h for h in header if h.strip()]

        # Check if we have a single non-empty cell that contains multiple keywords
        if len(non_empty_cells) == 1:
            header_text = non_empty_cells[0].lower()
            # Count how many column keywords are in this single cell
            keyword_count = 0
            for patterns in self.mapper.mappings.values():
                for pattern in patterns:
                    if pattern in header_text:
                        keyword_count += 1
                        break  # Only count once per field type

            logger.debug(f"has_merged_header: header_text='{header_text}', keyword_count={keyword_count}")
            return keyword_count >= 2

        return False

    def extract_from_merged_cells(
        self, header: list[str], rows: list[list[str]]
    ) -> list[LineItem]:
        """
        Extract line items from tables with merged cells.

        For poorly OCR'd tables like:
        Header: ["Specifikation 0218103-1201 2 rum och kök Hyra Avdrag"]
        Row 1: ["", "", "", "8159"]      <- amount row
        Row 2: ["", "", "", "-2 000"]    <- deduction row (separate line item)

        Or:
        Row: ["", "", "", "8159 -2 000"]  <- both in same row -> 2 line items

        Each amount becomes its own line item. Negative amounts are marked as is_deduction=True.
        """
        items = []

        # Amount pattern for Swedish format - match numbers like "8159" or "8 159" or "-2000" or "-2 000"
        amount_pattern = re.compile(
            r"(-?\d[\d\s]*(?:[,\.]\d+)?)"
        )

        # Try to parse header cell for description info
        header_text = " ".join(h for h in header if h.strip()) if header else ""
        logger.debug(f"extract_from_merged_cells: header_text='{header_text}'")
        logger.debug(f"extract_from_merged_cells: rows={rows}")

        # Extract description from header
        description = None
        article_number = None

        # Look for object number pattern (e.g., "0218103-1201")
        obj_match = re.search(r"(\d{7}-\d{4})", header_text)
        if obj_match:
            article_number = obj_match.group(1)

        # Look for description after object number
        desc_match = re.search(r"\d{7}-\d{4}\s+(.+?)(?:\s+(?:Hyra|Avdrag|Belopp))", header_text, re.IGNORECASE)
        if desc_match:
            description = desc_match.group(1).strip()

        row_index = 0
        for row in rows:
            # Combine all non-empty cells in the row
            row_text = " ".join(cell.strip() for cell in row if cell.strip())
            logger.debug(f"extract_from_merged_cells: row text='{row_text}'")

            if not row_text:
                continue

            # Find all amounts in the row
            amounts = amount_pattern.findall(row_text)
            logger.debug(f"extract_from_merged_cells: amounts={amounts}")

            for amt_str in amounts:
                # Clean the amount string
                cleaned = amt_str.replace(" ", "").strip()
                if not cleaned or cleaned == "-":
                    continue

                is_deduction = cleaned.startswith("-")

                # Skip small positive numbers that are likely not amounts
                # (e.g., row indices, small percentages)
                if not is_deduction:
                    try:
                        val = float(cleaned.replace(",", "."))
                        if val < MIN_AMOUNT_THRESHOLD:
                            continue
                    except ValueError:
                        continue

                # Create a line item for each amount
                item = LineItem(
                    row_index=row_index,
                    description=description if row_index == 0 else "Avdrag" if is_deduction else None,
                    article_number=article_number if row_index == 0 else None,
                    amount=cleaned,
                    is_deduction=is_deduction,
                    confidence=0.7,
                )
                items.append(item)
                row_index += 1
                logger.debug(f"extract_from_merged_cells: created item amount={cleaned}, is_deduction={is_deduction}")

        return items
