"""
Tests for table rendering components.

This module tests:
- TableStyle: Styling configuration for tables
- TableCellRenderer: Individual cell rendering
- TableRowRenderer: Row rendering with multiple cells
- TableRenderer: Complete table rendering
"""

import pytest
from PIL import Image, ImageDraw
from pyWebLayout.concrete.table import (
    TableStyle,
    TableCellRenderer,
    TableRowRenderer,
    TableRenderer
)
from pyWebLayout.abstract.block import (
    Table, TableRow, TableCell, Paragraph, Heading, HeadingLevel
)
from pyWebLayout.abstract.inline import Word
from pyWebLayout.style import Font


# ============================================================================
# Fixtures
# ============================================================================

@pytest.fixture
def sample_font():
    """Create a standard font for testing."""
    return Font(font_size=12, colour=(0, 0, 0))


@pytest.fixture
def sample_canvas():
    """Create a PIL canvas for rendering."""
    return Image.new('RGB', (800, 600), color=(255, 255, 255))


@pytest.fixture
def sample_draw(sample_canvas):
    """Create a PIL ImageDraw object."""
    return ImageDraw.Draw(sample_canvas)


@pytest.fixture
def default_table_style():
    """Create default table style."""
    return TableStyle()


@pytest.fixture
def custom_table_style():
    """Create custom table style."""
    return TableStyle(
        border_width=2,
        border_color=(100, 100, 100),
        cell_padding=(10, 10, 10, 10),
        header_bg_color=(200, 200, 200),
        cell_bg_color=(250, 250, 250),
        alternate_row_color=(240, 240, 240),
        cell_spacing=5
    )


@pytest.fixture
def simple_table(sample_font):
    """Create a simple table with header and body."""
    table = Table()
    table.caption = "Test Table"

    # Header row
    header_row = TableRow()
    header_cell1 = TableCell(is_header=True)
    header_p1 = Paragraph(sample_font)
    header_p1.add_word(Word("Column", sample_font))
    header_p1.add_word(Word("1", sample_font))
    header_cell1.add_block(header_p1)

    header_cell2 = TableCell(is_header=True)
    header_p2 = Paragraph(sample_font)
    header_p2.add_word(Word("Column", sample_font))
    header_p2.add_word(Word("2", sample_font))
    header_cell2.add_block(header_p2)

    header_row.add_cell(header_cell1)
    header_row.add_cell(header_cell2)
    table.add_row(header_row, section="header")

    # Body row
    body_row = TableRow()
    body_cell1 = TableCell()
    body_p1 = Paragraph(sample_font)
    body_p1.add_word(Word("Data", sample_font))
    body_p1.add_word(Word("1", sample_font))
    body_cell1.add_block(body_p1)

    body_cell2 = TableCell()
    body_p2 = Paragraph(sample_font)
    body_p2.add_word(Word("Data", sample_font))
    body_p2.add_word(Word("2", sample_font))
    body_cell2.add_block(body_p2)

    body_row.add_cell(body_cell1)
    body_row.add_cell(body_cell2)
    table.add_row(body_row, section="body")

    return table


# ============================================================================
# TableStyle Tests
# ============================================================================

class TestTableStyle:
    """Tests for TableStyle dataclass."""

    def test_default_initialization(self):
        """Test TableStyle with default values."""
        style = TableStyle()

        assert style.border_width == 1
        assert style.border_color == (0, 0, 0)
        assert style.cell_padding == (5, 5, 5, 5)
        assert style.header_bg_color == (240, 240, 240)
        assert style.header_text_bold is True
        assert style.cell_bg_color == (255, 255, 255)
        assert style.alternate_row_color == (250, 250, 250)
        assert style.cell_spacing == 0

    def test_custom_initialization(self):
        """Test TableStyle with custom values."""
        style = TableStyle(
            border_width=3,
            border_color=(255, 0, 0),
            cell_padding=(10, 15, 20, 25),
            header_bg_color=(100, 100, 100),
            header_text_bold=False,
            cell_bg_color=(200, 200, 200),
            alternate_row_color=None,
            cell_spacing=10
        )

        assert style.border_width == 3
        assert style.border_color == (255, 0, 0)
        assert style.cell_padding == (10, 15, 20, 25)
        assert style.header_bg_color == (100, 100, 100)
        assert style.header_text_bold is False
        assert style.cell_bg_color == (200, 200, 200)
        assert style.alternate_row_color is None
        assert style.cell_spacing == 10

    def test_all_attributes_accessible(self, custom_table_style):
        """Test that all style attributes are accessible."""
        # Verify all attributes exist and are accessible
        assert hasattr(custom_table_style, 'border_width')
        assert hasattr(custom_table_style, 'border_color')
        assert hasattr(custom_table_style, 'cell_padding')
        assert hasattr(custom_table_style, 'header_bg_color')
        assert hasattr(custom_table_style, 'header_text_bold')
        assert hasattr(custom_table_style, 'cell_bg_color')
        assert hasattr(custom_table_style, 'alternate_row_color')
        assert hasattr(custom_table_style, 'cell_spacing')


# ============================================================================
# TableCellRenderer Tests
# ============================================================================

class TestTableCellRenderer:
    """Tests for TableCellRenderer."""

    def test_initialization(self, sample_font, sample_draw, default_table_style):
        """Test TableCellRenderer initialization."""
        cell = TableCell()
        cell_renderer = TableCellRenderer(
            cell,
            origin=(10, 10),
            size=(100, 50),
            draw=sample_draw,
            style=default_table_style
        )

        assert cell_renderer._cell == cell
        # Origin and size may be numpy arrays, so compare values
        import numpy as np
        assert np.array_equal(cell_renderer._origin, (10, 10))
        assert np.array_equal(cell_renderer._size, (100, 50))
        assert cell_renderer._draw == sample_draw
        assert cell_renderer._style == default_table_style
        assert cell_renderer._is_header_section is False

    def test_initialization_with_header(
            self,
            sample_font,
            sample_draw,
            default_table_style):
        """Test TableCellRenderer initialization for header cell."""
        cell = TableCell(is_header=True)
        cell_renderer = TableCellRenderer(
            cell,
            origin=(10, 10),
            size=(100, 50),
            draw=sample_draw,
            style=default_table_style,
            is_header_section=True
        )

        assert cell_renderer._is_header_section is True

    def test_render_empty_cell(
            self,
            sample_font,
            sample_draw,
            sample_canvas,
            default_table_style):
        """Test rendering an empty cell."""
        cell = TableCell()
        cell_renderer = TableCellRenderer(
            cell,
            origin=(10, 10),
            size=(100, 50),
            draw=sample_draw,
            style=default_table_style,
            canvas=sample_canvas
        )

        result = cell_renderer.render()
        # Render returns None (draws directly on canvas)
        assert result is None

    def test_render_cell_with_text(
            self,
            sample_font,
            sample_draw,
            sample_canvas,
            default_table_style):
        """Test rendering a cell with text content."""
        cell = TableCell()
        paragraph = Paragraph(sample_font)
        paragraph.add_word(Word("Test", sample_font))
        paragraph.add_word(Word("Content", sample_font))
        cell.add_block(paragraph)

        cell_renderer = TableCellRenderer(
            cell,
            origin=(10, 10),
            size=(200, 50),
            draw=sample_draw,
            style=default_table_style,
            canvas=sample_canvas
        )

        result = cell_renderer.render()
        assert result is None

    def test_render_header_cell(
            self,
            sample_font,
            sample_draw,
            sample_canvas,
            default_table_style):
        """Test rendering a header cell with different styling."""
        cell = TableCell(is_header=True)
        paragraph = Paragraph(sample_font)
        paragraph.add_word(Word("Header", sample_font))
        cell.add_block(paragraph)

        cell_renderer = TableCellRenderer(
            cell,
            origin=(10, 10),
            size=(200, 50),
            draw=sample_draw,
            style=default_table_style,
            is_header_section=True,
            canvas=sample_canvas
        )

        result = cell_renderer.render()
        assert result is None

    def test_render_cell_with_heading(
            self,
            sample_font,
            sample_draw,
            sample_canvas,
            default_table_style):
        """Test rendering a cell with heading content."""
        cell = TableCell()
        heading = Heading(HeadingLevel.H2, sample_font)
        heading.add_word(Word("Heading", sample_font))
        cell.add_block(heading)

        cell_renderer = TableCellRenderer(
            cell,
            origin=(10, 10),
            size=(200, 50),
            draw=sample_draw,
            style=default_table_style,
            canvas=sample_canvas
        )

        result = cell_renderer.render()
        assert result is None

    def test_in_object(self, sample_font, sample_draw, default_table_style):
        """Test in_object method for point detection."""
        cell = TableCell()
        cell_renderer = TableCellRenderer(
            cell,
            origin=(10, 10),
            size=(100, 50),
            draw=sample_draw,
            style=default_table_style
        )

        # Point inside cell
        assert cell_renderer.in_object((50, 30))

        # Point outside cell
        assert not cell_renderer.in_object((150, 30))
        assert not cell_renderer.in_object((50, 100))

    def test_properties_access(self, sample_font, sample_draw, default_table_style):
        """Test accessing cell renderer properties."""
        import numpy as np
        cell = TableCell()
        cell_renderer = TableCellRenderer(
            cell,
            origin=(10, 10),
            size=(100, 50),
            draw=sample_draw,
            style=default_table_style
        )

        # May be numpy arrays
        assert np.array_equal(cell_renderer._origin, (10, 10))
        assert np.array_equal(cell_renderer._size, (100, 50))


# ============================================================================
# TableRowRenderer Tests
# ============================================================================

class TestTableRowRenderer:
    """Tests for TableRowRenderer."""

    def test_initialization(self, sample_font, sample_draw, default_table_style):
        """Test TableRowRenderer initialization."""
        row = TableRow()
        column_widths = [100, 150, 200]

        row_renderer = TableRowRenderer(
            row,
            origin=(10, 10),
            column_widths=column_widths,
            row_height=50,
            draw=sample_draw,
            style=default_table_style
        )

        assert row_renderer._row == row
        assert row_renderer._column_widths == column_widths
        assert row_renderer._row_height == 50
        assert row_renderer._draw == sample_draw
        assert row_renderer._style == default_table_style
        assert row_renderer._is_header_section is False

    def test_render_empty_row(self, sample_font, sample_draw, default_table_style):
        """Test rendering an empty row."""
        row = TableRow()

        row_renderer = TableRowRenderer(
            row,
            origin=(10, 10),
            column_widths=[100, 100],
            row_height=50,
            draw=sample_draw,
            style=default_table_style
        )

        result = row_renderer.render()
        assert result is None

    def test_render_row_with_cells(
            self,
            sample_font,
            sample_draw,
            sample_canvas,
            default_table_style):
        """Test rendering a row with multiple cells."""
        row = TableRow()

        # Add cells to row
        for i in range(3):
            cell = TableCell()
            paragraph = Paragraph(sample_font)
            paragraph.add_word(Word(f"Cell{i}", sample_font))
            cell.add_block(paragraph)
            row.add_cell(cell)

        row_renderer = TableRowRenderer(
            row,
            origin=(10, 10),
            column_widths=[100, 100, 100],
            row_height=50,
            draw=sample_draw,
            style=default_table_style,
            canvas=sample_canvas
        )

        result = row_renderer.render()
        assert result is None
        # Verify cells were created
        assert len(row_renderer._cell_renderers) == 3

    def test_render_row_with_colspan(
            self,
            sample_font,
            sample_draw,
            sample_canvas,
            default_table_style):
        """Test rendering a row with cells that span multiple columns."""
        row = TableRow()

        # Cell with colspan=2
        cell1 = TableCell(colspan=2)
        paragraph1 = Paragraph(sample_font)
        paragraph1.add_word(Word("Spanning", sample_font))
        cell1.add_block(paragraph1)
        row.add_cell(cell1)

        # Normal cell
        cell2 = TableCell()
        paragraph2 = Paragraph(sample_font)
        paragraph2.add_word(Word("Normal", sample_font))
        cell2.add_block(paragraph2)
        row.add_cell(cell2)

        row_renderer = TableRowRenderer(
            row,
            origin=(10, 10),
            column_widths=[100, 100, 100],
            row_height=50,
            draw=sample_draw,
            style=default_table_style,
            canvas=sample_canvas
        )

        result = row_renderer.render()
        assert result is None


# ============================================================================
# TableRenderer Tests
# ============================================================================

class TestTableRenderer:
    """Tests for TableRenderer."""

    def test_initialization(self, simple_table, sample_draw, default_table_style):
        """Test TableRenderer initialization."""
        import numpy as np
        table_renderer = TableRenderer(
            simple_table,
            origin=(10, 10),
            available_width=600,
            draw=sample_draw,
            style=default_table_style
        )

        assert table_renderer._table == simple_table
        assert np.array_equal(table_renderer._origin, (10, 10))
        assert table_renderer._available_width == 600
        assert table_renderer._draw == sample_draw
        assert table_renderer._style == default_table_style

    def test_dimension_calculation(
            self,
            simple_table,
            sample_draw,
            default_table_style):
        """Test table dimension calculation."""
        table_renderer = TableRenderer(
            simple_table,
            origin=(10, 10),
            available_width=600,
            draw=sample_draw,
            style=default_table_style
        )

        # Check that dimensions were calculated
        assert len(table_renderer._column_widths) == 2
        assert len(table_renderer._row_heights) == 3  # header, body, footer
        assert all(width > 0 for width in table_renderer._column_widths)

    def test_render_simple_table(
            self,
            simple_table,
            sample_draw,
            sample_canvas,
            default_table_style):
        """Test rendering a complete simple table."""
        table_renderer = TableRenderer(
            simple_table,
            origin=(10, 10),
            available_width=600,
            draw=sample_draw,
            style=default_table_style,
            canvas=sample_canvas
        )

        result = table_renderer.render()
        assert result is None
        # Verify rows were created
        assert len(table_renderer._row_renderers) == 2  # 1 header + 1 body

    def test_render_table_with_caption(
            self,
            simple_table,
            sample_draw,
            sample_canvas,
            default_table_style):
        """Test rendering a table with caption."""
        simple_table.caption = "Test Table Caption"

        table_renderer = TableRenderer(
            simple_table,
            origin=(10, 10),
            available_width=600,
            draw=sample_draw,
            style=default_table_style,
            canvas=sample_canvas
        )

        result = table_renderer.render()
        assert result is None

    def test_height_property(self, simple_table, sample_draw, default_table_style):
        """Test table height property."""
        table_renderer = TableRenderer(
            simple_table,
            origin=(10, 10),
            available_width=600,
            draw=sample_draw,
            style=default_table_style
        )

        height = table_renderer.height
        assert isinstance(height, int)
        assert height > 0

    def test_width_property(self, simple_table, sample_draw, default_table_style):
        """Test table width property."""
        table_renderer = TableRenderer(
            simple_table,
            origin=(10, 10),
            available_width=600,
            draw=sample_draw,
            style=default_table_style
        )

        width = table_renderer.width
        assert isinstance(width, int)
        assert width > 0
        assert width <= 600  # Should not exceed available width

    def test_empty_table(self, sample_draw, default_table_style):
        """Test rendering an empty table."""
        empty_table = Table()

        table_renderer = TableRenderer(
            empty_table,
            origin=(10, 10),
            available_width=600,
            draw=sample_draw,
            style=default_table_style
        )

        # Should handle gracefully
        assert table_renderer is not None

    def test_table_with_footer(
            self,
            sample_font,
            sample_draw,
            sample_canvas,
            default_table_style):
        """Test rendering a table with footer rows."""
        table = Table()

        # Add header
        header_row = TableRow()
        header_cell = TableCell(is_header=True)
        header_p = Paragraph(sample_font)
        header_p.add_word(Word("Header", sample_font))
        header_cell.add_block(header_p)
        header_row.add_cell(header_cell)
        table.add_row(header_row, section="header")

        # Add body
        body_row = TableRow()
        body_cell = TableCell()
        body_p = Paragraph(sample_font)
        body_p.add_word(Word("Body", sample_font))
        body_cell.add_block(body_p)
        body_row.add_cell(body_cell)
        table.add_row(body_row, section="body")

        # Add footer
        footer_row = TableRow()
        footer_cell = TableCell()
        footer_p = Paragraph(sample_font)
        footer_p.add_word(Word("Footer", sample_font))
        footer_cell.add_block(footer_p)
        footer_row.add_cell(footer_cell)
        table.add_row(footer_row, section="footer")

        table_renderer = TableRenderer(
            table,
            origin=(10, 10),
            available_width=600,
            draw=sample_draw,
            style=default_table_style,
            canvas=sample_canvas
        )

        result = table_renderer.render()
        assert result is None
        assert len(table_renderer._row_renderers) == 3  # header + body + footer

    def test_in_object(self, simple_table, sample_draw, default_table_style):
        """Test in_object method for table."""
        table_renderer = TableRenderer(
            simple_table,
            origin=(10, 10),
            available_width=600,
            draw=sample_draw,
            style=default_table_style
        )

        # Point inside table
        assert table_renderer.in_object((50, 50))

        # Point outside table
        assert not table_renderer.in_object((1000, 1000))


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
