"""Unit tests for merged cell resolution."""

import pytest
from openpyxl import Workbook
from openpyxl.worksheet.worksheet import Worksheet

from src.extraction.merged_cell_resolver import MergedCellResolver
from src.extraction.models import TableBoundary, MergeMetadata
from src.core.exceptions import ExtractionError


@pytest.fixture
def resolver():
    """Create a MergedCellResolver instance."""
    return MergedCellResolver()


@pytest.fixture
def sample_workbook():
    """Create a sample workbook for testing."""
    wb = Workbook()
    sheet = wb.active
    sheet.title = "TestSheet"
    return wb


class TestMergeMetadataModel:
    """Tests for MergeMetadata dataclass validation."""

    def test_valid_merge_metadata(self):
        """Should create valid MergeMetadata."""
        metadata = MergeMetadata(
            cell_coordinate="A2",
            merged_from_range="A2:A4",
            original_value="Group A",
        )

        assert metadata.cell_coordinate == "A2"
        assert metadata.merged_from_range == "A2:A4"
        assert metadata.original_value == "Group A"

    def test_to_dict(self):
        """Should convert MergeMetadata to dictionary."""
        metadata = MergeMetadata(
            cell_coordinate="B3",
            merged_from_range="B2:B5",
            original_value="Region North",
        )

        result = metadata.to_dict()

        assert result == {
            "cell_coordinate": "B3",
            "merged_from_range": "B2:B5",
            "original_value": "Region North",
        }

    def test_empty_cell_coordinate_raises_error(self):
        """Should raise error for empty cell coordinate."""
        with pytest.raises(ValueError, match="Cell coordinate cannot be empty"):
            MergeMetadata(
                cell_coordinate="",
                merged_from_range="A2:A4",
                original_value="Test",
            )

    def test_empty_merged_from_range_raises_error(self):
        """Should raise error for empty merged from range."""
        with pytest.raises(ValueError, match="Merged from range cannot be empty"):
            MergeMetadata(
                cell_coordinate="A2",
                merged_from_range="",
                original_value="Test",
            )

    def test_invalid_range_format_raises_error(self):
        """Should raise error for invalid range format."""
        with pytest.raises(ValueError, match="Invalid merged range format"):
            MergeMetadata(
                cell_coordinate="A2",
                merged_from_range="A2A4",  # Missing colon
                original_value="Test",
            )


class TestBasicMergedCellResolution:
    """Tests for basic merged cell resolution."""

    def test_resolve_simple_vertical_merge(self, resolver, sample_workbook):
        """Should resolve vertically merged cells (A2:A4)."""
        sheet = sample_workbook.active

        # Create data: merged cells A2:A4 with "Group A"
        sheet["A1"] = "Category"
        sheet["B1"] = "Value"
        sheet["A2"] = "Group A"
        sheet.merge_cells("A2:A4")
        sheet["B2"] = "100"
        sheet["B3"] = "200"
        sheet["B4"] = "300"

        boundary = TableBoundary(
            sheet="TestSheet",
            table_id=1,
            start_row=2,
            end_row=4,
            start_col="A",
            end_col="B",
        )

        resolved_grid, metadata = resolver.resolve_merged_cells(sheet, boundary)

        # Verify grid resolution
        assert len(resolved_grid) == 3  # 3 rows
        assert len(resolved_grid[0]) == 2  # 2 columns

        assert resolved_grid[0] == ["Group A", "100"]
        assert resolved_grid[1] == ["Group A", "200"]
        assert resolved_grid[2] == ["Group A", "300"]

        # Verify metadata (2 cells propagated from master A2)
        assert len(metadata) == 2
        assert metadata[0].cell_coordinate == "A3"
        assert metadata[0].merged_from_range == "A2:A4"
        assert metadata[0].original_value == "Group A"

        assert metadata[1].cell_coordinate == "A4"
        assert metadata[1].merged_from_range == "A2:A4"
        assert metadata[1].original_value == "Group A"

    def test_resolve_simple_horizontal_merge(self, resolver, sample_workbook):
        """Should resolve horizontally merged cells (B1:D1)."""
        sheet = sample_workbook.active

        # Create data: merged cells B1:D1 with "Sales"
        sheet["A1"] = "Product"
        sheet["B1"] = "Sales"
        sheet.merge_cells("B1:D1")
        sheet["A2"] = "Widget"
        sheet["B2"] = "Q1"
        sheet["C2"] = "Q2"
        sheet["D2"] = "Q3"

        boundary = TableBoundary(
            sheet="TestSheet",
            table_id=1,
            start_row=1,
            end_row=2,
            start_col="A",
            end_col="D",
        )

        resolved_grid, metadata = resolver.resolve_merged_cells(sheet, boundary)

        # Verify grid resolution
        assert len(resolved_grid) == 2  # 2 rows
        assert len(resolved_grid[0]) == 4  # 4 columns

        assert resolved_grid[0] == ["Product", "Sales", "Sales", "Sales"]
        assert resolved_grid[1] == ["Widget", "Q1", "Q2", "Q3"]

        # Verify metadata (2 cells propagated from master B1)
        assert len(metadata) == 2
        assert metadata[0].cell_coordinate == "C1"
        assert metadata[1].cell_coordinate == "D1"

    def test_resolve_block_merge(self, resolver, sample_workbook):
        """Should resolve block merged cells (B2:C3)."""
        sheet = sample_workbook.active

        # Create data: block merge B2:C3 with "Merged Block"
        sheet["A1"] = "Row"
        sheet["B1"] = "Col1"
        sheet["C1"] = "Col2"
        sheet["A2"] = "R1"
        sheet["B2"] = "Merged Block"
        sheet.merge_cells("B2:C3")
        sheet["A3"] = "R2"

        boundary = TableBoundary(
            sheet="TestSheet",
            table_id=1,
            start_row=2,
            end_row=3,
            start_col="A",
            end_col="C",
        )

        resolved_grid, metadata = resolver.resolve_merged_cells(sheet, boundary)

        # Verify grid resolution
        assert resolved_grid[0] == ["R1", "Merged Block", "Merged Block"]
        assert resolved_grid[1] == ["R2", "Merged Block", "Merged Block"]

        # Verify metadata (3 cells propagated: B3, C2, C3)
        assert len(metadata) == 3


class TestMergedCellFiltering:
    """Tests for filtering merged ranges by table boundary."""

    def test_filter_ranges_within_boundary(self, resolver, sample_workbook):
        """Should only resolve merged ranges within table boundary."""
        sheet = sample_workbook.active

        # Create merged cells outside and inside boundary
        sheet["A1"] = "Outside"
        sheet.merge_cells("A1:A2")  # Outside boundary

        sheet["A5"] = "Inside"
        sheet.merge_cells("A5:A7")  # Inside boundary

        boundary = TableBoundary(
            sheet="TestSheet",
            table_id=1,
            start_row=5,
            end_row=7,
            start_col="A",
            end_col="B",
        )

        resolved_grid, metadata = resolver.resolve_merged_cells(sheet, boundary)

        # Should only resolve A5:A7 (inside boundary)
        assert resolved_grid[0][0] == "Inside"
        assert resolved_grid[1][0] == "Inside"
        assert resolved_grid[2][0] == "Inside"

        # Metadata should only include cells from A5:A7 (2 propagated cells)
        assert len(metadata) == 2

    def test_filter_partial_intersection(self, resolver, sample_workbook):
        """Should handle merged ranges partially intersecting boundary."""
        sheet = sample_workbook.active

        # Merged range A2:A5, but boundary only covers A3:A4
        sheet["A2"] = "Partial"
        sheet.merge_cells("A2:A5")

        boundary = TableBoundary(
            sheet="TestSheet",
            table_id=1,
            start_row=3,
            end_row=4,
            start_col="A",
            end_col="B",
        )

        resolved_grid, metadata = resolver.resolve_merged_cells(sheet, boundary)

        # Should resolve A3 and A4 with master value
        assert resolved_grid[0][0] == "Partial"
        assert resolved_grid[1][0] == "Partial"

        # Metadata should include both A3 and A4 (master A2 is outside boundary)
        assert len(metadata) == 2


class TestEmptyMergedCells:
    """Tests for empty merged cell handling."""

    def test_empty_merged_cell_propagates_empty_string(self, resolver, sample_workbook):
        """Should propagate empty string for empty merged cells."""
        sheet = sample_workbook.active

        # Create merged cells with no value (None)
        sheet["A1"] = "Header"
        sheet.merge_cells("A2:A4")  # No value set, defaults to None

        boundary = TableBoundary(
            sheet="TestSheet",
            table_id=1,
            start_row=2,
            end_row=4,
            start_col="A",
            end_col="B",
        )

        resolved_grid, metadata = resolver.resolve_merged_cells(sheet, boundary)

        # Should propagate empty string
        assert resolved_grid[0][0] == ""
        assert resolved_grid[1][0] == ""
        assert resolved_grid[2][0] == ""

        # Metadata should still track propagation
        assert len(metadata) == 2
        assert metadata[0].original_value == ""


class TestSingleCellMerge:
    """Tests for edge case of single-cell 'merged' range."""

    def test_single_cell_merge_handled_correctly(self, resolver, sample_workbook):
        """Should handle single-cell merged range as normal cell."""
        sheet = sample_workbook.active

        # Create single-cell "merge" (edge case)
        sheet["A1"] = "Single"
        sheet.merge_cells("A1:A1")  # Single cell merge

        boundary = TableBoundary(
            sheet="TestSheet",
            table_id=1,
            start_row=1,
            end_row=1,
            start_col="A",
            end_col="B",
        )

        resolved_grid, metadata = resolver.resolve_merged_cells(sheet, boundary)

        # Should treat as normal cell
        assert resolved_grid[0][0] == "Single"

        # No metadata (master cell = only cell)
        assert len(metadata) == 0


class TestMultipleMergedRanges:
    """Tests for multiple merged ranges in same table."""

    def test_multiple_non_overlapping_ranges(self, resolver, sample_workbook):
        """Should resolve multiple non-overlapping merged ranges."""
        sheet = sample_workbook.active

        # Create multiple merged ranges
        sheet["A1"] = "Group1"
        sheet.merge_cells("A1:A2")

        sheet["B1"] = "Group2"
        sheet.merge_cells("B1:B2")

        sheet["C1"] = "Group3"
        sheet.merge_cells("C1:C2")

        boundary = TableBoundary(
            sheet="TestSheet",
            table_id=1,
            start_row=1,
            end_row=2,
            start_col="A",
            end_col="C",
        )

        resolved_grid, metadata = resolver.resolve_merged_cells(sheet, boundary)

        # Verify all ranges resolved
        assert resolved_grid[0] == ["Group1", "Group2", "Group3"]
        assert resolved_grid[1] == ["Group1", "Group2", "Group3"]

        # 3 ranges, 1 propagated cell each = 3 metadata entries
        assert len(metadata) == 3


class TestEdgeCases:
    """Tests for edge cases and error handling."""

    def test_no_merged_cells_in_boundary(self, resolver, sample_workbook):
        """Should handle table with no merged cells."""
        sheet = sample_workbook.active

        # Create regular table with no merges
        sheet["A1"] = "Name"
        sheet["B1"] = "Value"
        sheet["A2"] = "Item1"
        sheet["B2"] = "100"

        boundary = TableBoundary(
            sheet="TestSheet",
            table_id=1,
            start_row=1,
            end_row=2,
            start_col="A",
            end_col="B",
        )

        resolved_grid, metadata = resolver.resolve_merged_cells(sheet, boundary)

        # Grid should contain original values
        assert resolved_grid[0] == ["Name", "Value"]
        assert resolved_grid[1] == ["Item1", "100"]

        # No metadata (no merges)
        assert len(metadata) == 0

    def test_boundary_outside_sheet_data(self, resolver, sample_workbook):
        """Should handle boundary extending beyond sheet data."""
        sheet = sample_workbook.active

        # Small data
        sheet["A1"] = "Data"

        # Boundary extends beyond
        boundary = TableBoundary(
            sheet="TestSheet",
            table_id=1,
            start_row=1,
            end_row=10,
            start_col="A",
            end_col="E",
        )

        resolved_grid, metadata = resolver.resolve_merged_cells(sheet, boundary)

        # Should handle gracefully with empty cells
        assert len(resolved_grid) == 10
        assert len(resolved_grid[0]) == 5
        assert resolved_grid[0][0] == "Data"
        assert resolved_grid[0][1] == ""  # Empty cell

    def test_overlapping_merged_ranges_logs_warning(
        self, resolver, sample_workbook, caplog
    ):
        """Should log warning for overlapping merged ranges."""
        import logging

        caplog.set_level(logging.WARNING)

        sheet = sample_workbook.active

        # Create first merged range
        sheet["A1"] = "First"
        sheet.merge_cells("A1:B2")

        # Manually create overlapping range (not possible via API normally)
        # Instead test with adjacent ranges that might conflict
        sheet["C1"] = "Second"
        sheet.merge_cells("C1:C2")

        boundary = TableBoundary(
            sheet="TestSheet",
            table_id=1,
            start_row=1,
            end_row=2,
            start_col="A",
            end_col="C",
        )

        resolved_grid, metadata = resolver.resolve_merged_cells(sheet, boundary)

        # Should resolve both ranges
        assert resolved_grid[0] == ["First", "First", "Second"]
        assert resolved_grid[1] == ["First", "First", "Second"]


class TestComplexScenarios:
    """Tests for complex real-world scenarios."""

    def test_mixed_merged_and_regular_cells(self, resolver, sample_workbook):
        """Should handle mix of merged and regular cells."""
        sheet = sample_workbook.active

        # Mix of merged and regular cells
        sheet["A1"] = "Category"
        sheet.merge_cells("A1:A3")

        sheet["B1"] = "Q1"
        sheet["B2"] = "Q2"
        sheet["B3"] = "Q3"

        sheet["C1"] = "Total"
        sheet.merge_cells("C1:C3")

        boundary = TableBoundary(
            sheet="TestSheet",
            table_id=1,
            start_row=1,
            end_row=3,
            start_col="A",
            end_col="C",
        )

        resolved_grid, metadata = resolver.resolve_merged_cells(sheet, boundary)

        # Verify resolution
        assert resolved_grid[0] == ["Category", "Q1", "Total"]
        assert resolved_grid[1] == ["Category", "Q2", "Total"]
        assert resolved_grid[2] == ["Category", "Q3", "Total"]

        # Metadata: A2, A3, C2, C3 (4 propagated cells)
        assert len(metadata) == 4

    def test_large_merged_block(self, resolver, sample_workbook):
        """Should handle large merged block efficiently."""
        sheet = sample_workbook.active

        # Large merged block A1:E10
        sheet["A1"] = "Large Block"
        sheet.merge_cells("A1:E10")

        boundary = TableBoundary(
            sheet="TestSheet",
            table_id=1,
            start_row=1,
            end_row=10,
            start_col="A",
            end_col="E",
        )

        resolved_grid, metadata = resolver.resolve_merged_cells(sheet, boundary)

        # All cells should have propagated value
        for row in resolved_grid:
            for cell_value in row:
                assert cell_value == "Large Block"

        # Metadata: 50 cells - 1 master = 49 propagated
        assert len(metadata) == 49
