"""Unit tests for the generator module."""

import os
import tempfile

import pytest
from PIL import Image

from retroflow.generator import FlowchartGenerator
from retroflow.renderer import (
    ARROW_CHARS,
    BOX_CHARS,
    BOX_CHARS_DOUBLE,
    DASHED_BOX_CHARS,
)


class TestFlowchartGeneratorInit:
    """Tests for FlowchartGenerator initialization."""

    def test_default_initialization(self):
        """Test FlowchartGenerator with default parameters."""
        gen = FlowchartGenerator()
        assert gen.max_text_width == 22
        assert gen.min_box_width == 10
        assert gen.horizontal_spacing == 12
        assert gen.vertical_spacing == 3
        assert gen.shadow is True
        assert gen.rounded is False
        assert gen.compact is True
        assert gen.font is None
        assert gen.title is None
        assert gen.direction == "TB"

    def test_custom_max_text_width(self):
        """Test FlowchartGenerator with custom max_text_width."""
        gen = FlowchartGenerator(max_text_width=30)
        assert gen.max_text_width == 30

    def test_custom_min_box_width(self):
        """Test FlowchartGenerator with custom min_box_width."""
        gen = FlowchartGenerator(min_box_width=15)
        assert gen.min_box_width == 15

    def test_custom_horizontal_spacing(self):
        """Test FlowchartGenerator with custom horizontal_spacing."""
        gen = FlowchartGenerator(horizontal_spacing=20)
        assert gen.horizontal_spacing == 20

    def test_custom_vertical_spacing(self):
        """Test FlowchartGenerator with custom vertical_spacing."""
        gen = FlowchartGenerator(vertical_spacing=10)
        assert gen.vertical_spacing == 10

    def test_shadow_disabled(self):
        """Test FlowchartGenerator with shadow disabled."""
        gen = FlowchartGenerator(shadow=False)
        assert gen.shadow is False

    def test_rounded_corners_enabled(self):
        """Test FlowchartGenerator with rounded corners enabled."""
        gen = FlowchartGenerator(rounded=True)
        assert gen.rounded is True

    def test_compact_mode_enabled(self):
        """Test FlowchartGenerator with compact mode enabled."""
        gen = FlowchartGenerator(compact=True)
        assert gen.compact is True

    def test_custom_font(self):
        """Test FlowchartGenerator with custom font."""
        gen = FlowchartGenerator(font="Cascadia Code")
        assert gen.font == "Cascadia Code"

    def test_all_custom_parameters(self):
        """Test FlowchartGenerator with all custom parameters."""
        gen = FlowchartGenerator(
            max_text_width=25,
            min_box_width=12,
            horizontal_spacing=15,
            vertical_spacing=8,
            shadow=False,
        )
        assert gen.max_text_width == 25
        assert gen.min_box_width == 12
        assert gen.horizontal_spacing == 15
        assert gen.vertical_spacing == 8
        assert gen.shadow is False

    def test_components_initialized(self):
        """Test that internal components are initialized."""
        gen = FlowchartGenerator()
        assert gen.parser is not None
        assert gen.layout_engine is not None
        assert gen.box_renderer is not None
        assert gen.title_renderer is not None

    def test_title_initialization(self):
        """Test FlowchartGenerator with title parameter."""
        gen = FlowchartGenerator(title="My Flowchart")
        assert gen.title == "My Flowchart"

    def test_direction_tb(self):
        """Test FlowchartGenerator with TB direction."""
        gen = FlowchartGenerator(direction="TB")
        assert gen.direction == "TB"

    def test_direction_lr(self):
        """Test FlowchartGenerator with LR direction."""
        gen = FlowchartGenerator(direction="LR")
        assert gen.direction == "LR"

    def test_direction_lowercase(self):
        """Test FlowchartGenerator normalizes direction to uppercase."""
        gen = FlowchartGenerator(direction="lr")
        assert gen.direction == "LR"

    def test_direction_invalid(self):
        """Test FlowchartGenerator raises error for invalid direction."""
        with pytest.raises(ValueError) as excinfo:
            FlowchartGenerator(direction="invalid")
        assert "direction must be" in str(excinfo.value)


class TestFlowchartGeneratorGenerate:
    """Tests for FlowchartGenerator.generate method."""

    def test_generate_simple_linear(self, generator, simple_input):
        """Test generating simple linear flowchart."""
        result = generator.generate(simple_input)
        assert isinstance(result, str)
        assert len(result) > 0

    def test_generate_contains_node_names(self, generator, simple_input):
        """Test that generated output contains node names."""
        result = generator.generate(simple_input)
        assert "A" in result
        assert "B" in result
        assert "C" in result
        assert "D" in result

    def test_generate_contains_box_characters(self, generator, simple_input):
        """Test that generated output contains box characters."""
        result = generator.generate(simple_input)
        assert BOX_CHARS["top_left"] in result
        assert BOX_CHARS["top_right"] in result
        assert BOX_CHARS["bottom_left"] in result
        assert BOX_CHARS["bottom_right"] in result

    def test_generate_contains_arrows(self, generator, simple_input):
        """Test that generated output contains arrow characters."""
        result = generator.generate(simple_input)
        assert ARROW_CHARS["down"] in result

    def test_generate_contains_shadows(self, generator, simple_input):
        """Test that generated output contains shadow characters."""
        result = generator.generate(simple_input)
        assert BOX_CHARS["shadow"] in result

    def test_generate_without_shadows(self, simple_input):
        """Test generating without shadows."""
        gen = FlowchartGenerator(shadow=False)
        result = gen.generate(simple_input)
        assert BOX_CHARS["shadow"] not in result

    def test_generate_with_rounded_corners(self, simple_input):
        """Test generating with rounded corners."""
        from retroflow.renderer import BOX_CHARS_ROUNDED

        gen = FlowchartGenerator(rounded=True)
        result = gen.generate(simple_input)
        # Should contain rounded corner characters
        assert BOX_CHARS_ROUNDED["top_left"] in result
        assert BOX_CHARS_ROUNDED["top_right"] in result
        # Should NOT contain square corner characters
        assert BOX_CHARS["top_left"] not in result

    def test_generate_with_compact_mode(self, simple_input):
        """Test generating with compact mode produces smaller output."""
        gen_padded = FlowchartGenerator(compact=False)
        gen_compact = FlowchartGenerator(compact=True)

        result_padded = gen_padded.generate(simple_input)
        result_compact = gen_compact.generate(simple_input)

        # Compact mode should produce fewer lines
        lines_padded = result_padded.count("\n")
        lines_compact = result_compact.count("\n")
        assert lines_compact < lines_padded

    def test_generate_branching(self, generator, branching_input):
        """Test generating branching flowchart."""
        result = generator.generate(branching_input)
        assert "Start" in result
        assert "Process1" in result
        assert "Process2" in result
        assert "End" in result

    def test_generate_cyclic(self, generator, cyclic_input):
        """Test generating cyclic flowchart."""
        result = generator.generate(cyclic_input)
        assert "A" in result
        assert "B" in result
        assert "C" in result
        # Should have arrows for back edges too
        assert ARROW_CHARS["right"] in result or ARROW_CHARS["down"] in result

    def test_generate_complex(self, generator, complex_input):
        """Test generating complex flowchart."""
        result = generator.generate(complex_input)
        assert "Init" in result
        assert "Validate" in result
        assert "Process" in result
        assert "Done" in result

    def test_generate_with_spaces_in_names(self, generator):
        """Test generating with multi-word node names."""
        input_text = """
        Start Here -> Process Data
        Process Data -> End Here
        """
        result = generator.generate(input_text)
        assert "Start Here" in result
        assert "Process Data" in result
        assert "End Here" in result

    def test_generate_single_edge(self, generator):
        """Test generating with single edge."""
        result = generator.generate("A -> B")
        assert "A" in result
        assert "B" in result

    def test_generate_long_chain(self, generator):
        """Test generating a long chain of nodes."""
        input_text = """
        N1 -> N2
        N2 -> N3
        N3 -> N4
        N4 -> N5
        N5 -> N6
        """
        result = generator.generate(input_text)
        for i in range(1, 7):
            assert f"N{i}" in result


class TestFlowchartGeneratorSaveTxt:
    """Tests for FlowchartGenerator.save_txt method."""

    def test_save_txt_creates_file(self, generator, simple_input):
        """Test that save_txt creates a file."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
            filename = f.name

        try:
            generator.save_txt(simple_input, filename)
            assert os.path.exists(filename)
        finally:
            if os.path.exists(filename):
                os.remove(filename)

    def test_save_txt_content(self, generator, simple_input):
        """Test that save_txt writes correct content."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
            filename = f.name

        try:
            generator.save_txt(simple_input, filename)
            with open(filename, "r", encoding="utf-8") as f:
                content = f.read()
            assert "A" in content
            assert "B" in content
            assert BOX_CHARS["top_left"] in content
        finally:
            if os.path.exists(filename):
                os.remove(filename)

    def test_save_txt_boxes_only_parameter(self, generator, simple_input):
        """Test that boxes_only parameter is accepted (but ignored)."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
            filename = f.name

        try:
            # Should not raise even with boxes_only parameter
            generator.save_txt(simple_input, filename, boxes_only=True)
            assert os.path.exists(filename)
        finally:
            if os.path.exists(filename):
                os.remove(filename)

    def test_save_txt_overwrites_existing(self, generator, simple_input):
        """Test that save_txt overwrites existing file."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".txt", delete=False) as f:
            f.write("Original content")
            filename = f.name

        try:
            generator.save_txt(simple_input, filename)
            with open(filename, "r", encoding="utf-8") as f:
                content = f.read()
            assert "Original content" not in content
            assert "A" in content
        finally:
            if os.path.exists(filename):
                os.remove(filename)


class TestFlowchartGeneratorSavePng:
    """Tests for FlowchartGenerator.save_png method."""

    def test_save_png_creates_file(self, generator, simple_input):
        """Test that save_png creates a file."""
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
            filename = f.name

        try:
            generator.save_png(simple_input, filename)
            assert os.path.exists(filename)
        finally:
            if os.path.exists(filename):
                os.remove(filename)

    def test_save_png_creates_valid_image(self, generator, simple_input):
        """Test that save_png creates a valid PNG image."""
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
            filename = f.name

        try:
            generator.save_png(simple_input, filename)
            # Should be openable as a valid image
            with Image.open(filename) as img:
                assert img.format == "PNG"
                assert img.mode == "RGB"
                assert img.width > 0
                assert img.height > 0
        finally:
            if os.path.exists(filename):
                os.remove(filename)

    def test_save_png_custom_font_size(self, generator, simple_input):
        """Test that larger font_size produces larger image."""
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
            filename_small = f.name
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
            filename_large = f.name

        try:
            generator.save_png(simple_input, filename_small, font_size=12)
            generator.save_png(simple_input, filename_large, font_size=24)

            with Image.open(filename_small) as img_small:
                small_width, small_height = img_small.width, img_small.height
            with Image.open(filename_large) as img_large:
                large_width, large_height = img_large.width, img_large.height

            # Larger font should produce larger image
            assert large_width > small_width
            assert large_height > small_height
        finally:
            for fn in [filename_small, filename_large]:
                if os.path.exists(fn):
                    os.remove(fn)

    def test_save_png_custom_colors(self, generator, simple_input):
        """Test save_png with custom background and foreground colors."""
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
            filename = f.name

        try:
            generator.save_png(
                simple_input,
                filename,
                bg_color="#000000",
                fg_color="#00FF00",
            )
            with Image.open(filename) as img:
                # Check that image was created with correct format
                assert img.format == "PNG"
                assert img.mode == "RGB"

                # Check corners are the background color (black)
                pixels = img.load()
                # Top-left corner should be background color
                assert pixels[0, 0] == (0, 0, 0)
        finally:
            if os.path.exists(filename):
                os.remove(filename)

    def test_save_png_custom_padding(self, generator, simple_input):
        """Test that larger padding produces larger image."""
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
            filename_small = f.name
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
            filename_large = f.name

        try:
            generator.save_png(simple_input, filename_small, padding=10)
            generator.save_png(simple_input, filename_large, padding=50)

            with Image.open(filename_small) as img_small:
                small_width, small_height = img_small.width, img_small.height
            with Image.open(filename_large) as img_large:
                large_width, large_height = img_large.width, img_large.height

            # Larger padding should produce larger image
            assert large_width > small_width
            assert large_height > small_height
        finally:
            for fn in [filename_small, filename_large]:
                if os.path.exists(fn):
                    os.remove(fn)

    def test_save_png_with_invalid_font(self, generator, simple_input):
        """Test that invalid font name falls back gracefully."""
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
            filename = f.name

        try:
            # Should not raise, should fall back to system/default font
            generator.save_png(
                simple_input,
                filename,
                font="NonexistentFontName12345",
            )
            assert os.path.exists(filename)
            with Image.open(filename) as img:
                assert img.format == "PNG"
        finally:
            if os.path.exists(filename):
                os.remove(filename)

    def test_save_png_with_instance_font(self, simple_input):
        """Test that instance font is used when set."""
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
            filename = f.name

        try:
            # Create generator with font specified
            gen = FlowchartGenerator(font="DejaVu Sans Mono")
            gen.save_png(simple_input, filename)
            assert os.path.exists(filename)
            with Image.open(filename) as img:
                assert img.format == "PNG"
        finally:
            if os.path.exists(filename):
                os.remove(filename)

    def test_save_png_overwrites_existing(self, generator, simple_input):
        """Test that save_png overwrites existing file."""
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
            filename = f.name
            # Write some dummy data
            f.write(b"dummy content")

        try:
            generator.save_png(simple_input, filename)
            # File should now be a valid PNG, not the dummy content
            with Image.open(filename) as img:
                assert img.format == "PNG"
        finally:
            if os.path.exists(filename):
                os.remove(filename)

    def test_save_png_with_branching(self, generator, branching_input):
        """Test save_png with branching flowchart."""
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
            filename = f.name

        try:
            generator.save_png(branching_input, filename)
            with Image.open(filename) as img:
                assert img.format == "PNG"
                # Branching flowchart should be wider
                assert img.width > 100
        finally:
            if os.path.exists(filename):
                os.remove(filename)

    def test_save_png_with_cyclic(self, generator, cyclic_input):
        """Test save_png with cyclic flowchart."""
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
            filename = f.name

        try:
            generator.save_png(cyclic_input, filename)
            with Image.open(filename) as img:
                assert img.format == "PNG"
        finally:
            if os.path.exists(filename):
                os.remove(filename)

    def test_save_png_minimum_dimensions(self, generator):
        """Test that save_png enforces minimum image dimensions."""
        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
            filename = f.name

        try:
            # Very simple input
            generator.save_png("A -> B", filename, font_size=8, padding=5)
            with Image.open(filename) as img:
                # Should enforce minimum dimensions
                assert img.width >= 100
                assert img.height >= 100
        finally:
            if os.path.exists(filename):
                os.remove(filename)


class TestFlowchartGeneratorLoadFont:
    """Tests for FlowchartGenerator._load_monospace_font method."""

    def test_load_font_returns_font(self, generator):
        """Test that _load_monospace_font returns a font object."""
        font = generator.exporter._load_monospace_font(16)
        assert font is not None
        # Should be able to get bounding box
        bbox = font.getbbox("M")
        assert bbox is not None
        assert len(bbox) == 4

    def test_load_font_respects_size(self, generator):
        """Test that different font sizes produce different bbox sizes."""
        font_small = generator.exporter._load_monospace_font(12)
        font_large = generator.exporter._load_monospace_font(24)

        bbox_small = font_small.getbbox("M")
        bbox_large = font_large.getbbox("M")

        # Larger font should have larger bounding box
        small_width = bbox_small[2] - bbox_small[0]
        large_width = bbox_large[2] - bbox_large[0]
        assert large_width > small_width

    def test_load_font_with_invalid_name(self, generator):
        """Test that invalid font name falls through to system fonts."""
        font = generator.exporter._load_monospace_font(16, "NonexistentFont12345")
        assert font is not None
        # Should still be usable
        bbox = font.getbbox("M")
        assert bbox is not None

    def test_load_font_with_valid_name(self, generator):
        """Test loading font by name."""
        # Try a font that should exist on most systems
        font = generator.exporter._load_monospace_font(16, "DejaVu Sans Mono")
        assert font is not None
        bbox = font.getbbox("M")
        assert bbox is not None


class TestFlowchartGeneratorInternalMethods:
    """Tests for FlowchartGenerator internal methods."""

    def test_calculate_all_box_dimensions(self, generator):
        """Test calculate_all_box_dimensions method."""
        input_text = "A -> B"
        connections = generator.parser.parse(input_text)
        layout_result = generator.layout_engine.layout(connections)

        dimensions = generator.position_calculator.calculate_all_box_dimensions(
            layout_result
        )

        assert "A" in dimensions
        assert "B" in dimensions
        assert dimensions["A"].width >= generator.min_box_width
        assert dimensions["B"].width >= generator.min_box_width

    def test_min_box_width_enforced(self, generator):
        """Test that minimum box width is enforced."""
        # Use a very short node name
        input_text = "X -> Y"
        connections = generator.parser.parse(input_text)
        layout_result = generator.layout_engine.layout(connections)

        dimensions = generator.position_calculator.calculate_all_box_dimensions(
            layout_result
        )

        assert dimensions["X"].width >= generator.min_box_width
        assert dimensions["Y"].width >= generator.min_box_width

    def test_calculate_positions(self, generator):
        """Test calculate_positions method."""
        input_text = "A -> B\nB -> C"
        connections = generator.parser.parse(input_text)
        layout_result = generator.layout_engine.layout(connections)
        box_dimensions = generator.position_calculator.calculate_all_box_dimensions(
            layout_result
        )

        positions = generator.position_calculator.calculate_positions(
            layout_result, box_dimensions
        )

        assert "A" in positions
        assert "B" in positions
        assert "C" in positions

        # Each position should be (x, y) tuple
        for name, pos in positions.items():
            assert isinstance(pos, tuple)
            assert len(pos) == 2

    def test_calculate_positions_with_margin(self, generator):
        """Test calculate_positions with left margin."""
        input_text = "A -> B"
        connections = generator.parser.parse(input_text)
        layout_result = generator.layout_engine.layout(connections)
        box_dimensions = generator.position_calculator.calculate_all_box_dimensions(
            layout_result
        )

        positions_no_margin = generator.position_calculator.calculate_positions(
            layout_result, box_dimensions, left_margin=0
        )
        positions_with_margin = generator.position_calculator.calculate_positions(
            layout_result, box_dimensions, left_margin=10
        )

        # With margin, x positions should be shifted
        for name in positions_no_margin:
            assert positions_with_margin[name][0] >= positions_no_margin[name][0]

    def test_calculate_canvas_size(self, generator):
        """Test calculate_canvas_size method."""
        input_text = "A -> B"
        connections = generator.parser.parse(input_text)
        layout_result = generator.layout_engine.layout(connections)
        box_dimensions = generator.position_calculator.calculate_all_box_dimensions(
            layout_result
        )
        box_positions = generator.position_calculator.calculate_positions(
            layout_result, box_dimensions
        )

        width, height = generator.position_calculator.calculate_canvas_size(
            box_dimensions, box_positions
        )

        assert width > 0
        assert height > 0

    def test_calculate_port_x_single(self, generator):
        """Test calculate_port_x for single port."""
        box_x = 10
        box_width = 20

        port_x = generator.position_calculator.calculate_port_x(box_x, box_width, 0, 1)

        # Single port should be centered
        expected_center = box_x + box_width // 2
        assert port_x == expected_center

    def test_calculate_port_x_multiple(self, generator):
        """Test calculate_port_x for multiple ports."""
        box_x = 10
        box_width = 20

        calc = generator.position_calculator
        port_x_0 = calc.calculate_port_x(box_x, box_width, 0, 3)
        port_x_1 = calc.calculate_port_x(box_x, box_width, 1, 3)
        port_x_2 = calc.calculate_port_x(box_x, box_width, 2, 3)

        # Ports should be distributed
        assert port_x_0 < port_x_1 < port_x_2


class TestFlowchartGeneratorEdgeCases:
    """Tests for edge cases and special scenarios."""

    def test_generate_with_back_edges(self, generator):
        """Test generation with back edges (cycles)."""
        input_text = """
        A -> B
        B -> C
        C -> B
        """
        result = generator.generate(input_text)
        assert "A" in result
        assert "B" in result
        assert "C" in result

    def test_generate_diamond_pattern(self, generator):
        """Test generation with diamond pattern."""
        input_text = """
        A -> B
        A -> C
        B -> D
        C -> D
        """
        result = generator.generate(input_text)
        assert "A" in result
        assert "B" in result
        assert "C" in result
        assert "D" in result

    def test_generate_wide_branching(self, generator):
        """Test generation with wide branching."""
        input_text = """
        Start -> A
        Start -> B
        Start -> C
        Start -> D
        """
        result = generator.generate(input_text)
        assert "Start" in result
        assert "A" in result
        assert "D" in result

    def test_generate_deep_nesting(self, generator):
        """Test generation with deep nesting."""
        input_text = """
        L1 -> L2
        L2 -> L3
        L3 -> L4
        L4 -> L5
        L5 -> L6
        L6 -> L7
        L7 -> L8
        """
        result = generator.generate(input_text)
        for i in range(1, 9):
            assert f"L{i}" in result

    def test_generate_multiple_roots(self, generator):
        """Test generation with multiple root nodes."""
        input_text = """
        A -> C
        B -> C
        """
        result = generator.generate(input_text)
        assert "A" in result
        assert "B" in result
        assert "C" in result

    def test_generate_multiple_sinks(self, generator):
        """Test generation with multiple sink nodes."""
        input_text = """
        A -> B
        A -> C
        """
        result = generator.generate(input_text)
        assert "A" in result
        assert "B" in result
        assert "C" in result


class TestFlowchartGeneratorTitle:
    """Tests for FlowchartGenerator title feature."""

    def test_title_in_output(self):
        """Test that title appears in output."""
        gen = FlowchartGenerator(title="My Flowchart")
        result = gen.generate("A -> B")
        assert "My Flowchart" in result

    def test_title_uses_double_line_border(self):
        """Test that title uses double-line border characters."""
        gen = FlowchartGenerator(title="Test Title")
        result = gen.generate("A -> B")
        assert BOX_CHARS_DOUBLE["top_left"] in result
        assert BOX_CHARS_DOUBLE["top_right"] in result
        assert BOX_CHARS_DOUBLE["bottom_left"] in result
        assert BOX_CHARS_DOUBLE["bottom_right"] in result
        assert BOX_CHARS_DOUBLE["horizontal"] in result
        assert BOX_CHARS_DOUBLE["vertical"] in result

    def test_title_override_in_generate(self):
        """Test that title can be overridden in generate call."""
        gen = FlowchartGenerator(title="Instance Title")
        result = gen.generate("A -> B", title="Override Title")
        assert "Override Title" in result
        assert "Instance Title" not in result

    def test_no_title_when_none(self):
        """Test that no title banner appears when title is None."""
        gen = FlowchartGenerator()
        result = gen.generate("A -> B")
        # Double-line characters should not appear
        assert BOX_CHARS_DOUBLE["top_left"] not in result
        assert BOX_CHARS_DOUBLE["vertical"] not in result

    def test_title_with_long_text(self):
        """Test title with long text wraps at word boundaries."""
        gen = FlowchartGenerator(title="This is a very long title for the flowchart")
        result = gen.generate("A -> B")
        # Title wraps at ~15 chars, so it should appear on multiple lines
        # Check that key parts of the title are present (may be split across lines)
        assert "This is a very" in result
        assert "long title for" in result
        assert "the flowchart" in result

    def test_title_above_flowchart(self):
        """Test that title appears above the flowchart nodes."""
        gen = FlowchartGenerator(title="Title")
        result = gen.generate("A -> B")
        lines = result.split("\n")

        # Find the line with the title
        title_line_idx = None
        for i, line in enumerate(lines):
            if "Title" in line:
                title_line_idx = i
                break

        # Find the first line with node A
        node_a_line_idx = None
        for i, line in enumerate(lines):
            if "A" in line and BOX_CHARS["vertical"] in line:
                node_a_line_idx = i
                break

        # Title should be above the node
        assert title_line_idx is not None
        assert node_a_line_idx is not None
        assert title_line_idx < node_a_line_idx


class TestFlowchartGeneratorHorizontalMode:
    """Tests for FlowchartGenerator horizontal (LR) flow mode."""

    def test_horizontal_mode_generates_output(self):
        """Test that horizontal mode generates valid output."""
        gen = FlowchartGenerator(direction="LR")
        result = gen.generate("A -> B\nB -> C")
        assert "A" in result
        assert "B" in result
        assert "C" in result

    def test_horizontal_mode_uses_right_arrows(self):
        """Test that horizontal mode uses right arrows."""
        gen = FlowchartGenerator(direction="LR")
        result = gen.generate("A -> B")
        assert ARROW_CHARS["right"] in result

    def test_horizontal_mode_layout(self):
        """Test that horizontal mode arranges nodes left to right."""
        gen = FlowchartGenerator(direction="LR")
        result = gen.generate("A -> B\nB -> C")
        lines = result.split("\n")

        # Find positions of A, B, C in the output
        a_col = None
        b_col = None
        c_col = None

        for line in lines:
            if "A" in line and a_col is None:
                a_col = line.index("A")
            if "B" in line and b_col is None:
                b_col = line.index("B")
            if "C" in line and c_col is None:
                c_col = line.index("C")

        # In LR mode, A should be to the left of B, B to the left of C
        assert a_col is not None
        assert b_col is not None
        assert c_col is not None
        assert a_col < b_col < c_col

    def test_horizontal_mode_with_branching(self):
        """Test horizontal mode with branching flowchart."""
        gen = FlowchartGenerator(direction="LR")
        input_text = """
        Start -> A
        Start -> B
        A -> End
        B -> End
        """
        result = gen.generate(input_text)
        assert "Start" in result
        assert "A" in result
        assert "B" in result
        assert "End" in result

    def test_horizontal_mode_with_cycles(self):
        """Test horizontal mode with cyclic flowchart."""
        gen = FlowchartGenerator(direction="LR")
        input_text = """
        A -> B
        B -> C
        C -> A
        """
        result = gen.generate(input_text)
        assert "A" in result
        assert "B" in result
        assert "C" in result

    def test_horizontal_mode_with_title(self):
        """Test horizontal mode combined with title."""
        gen = FlowchartGenerator(direction="LR", title="Horizontal Flow")
        result = gen.generate("A -> B\nB -> C")
        # Title may wrap
        assert "Horizontal" in result
        assert BOX_CHARS_DOUBLE["top_left"] in result
        assert ARROW_CHARS["right"] in result

    def test_horizontal_mode_with_long_title(self):
        """Test horizontal mode with longer title that needs centering offset."""
        gen = FlowchartGenerator(
            direction="LR", title="Very Long Title For The Diagram"
        )
        result = gen.generate("A -> B\nB -> C\nC -> D")
        # Should generate without errors
        assert "A" in result
        assert "B" in result
        assert "C" in result
        assert "D" in result

    def test_vertical_vs_horizontal_different_dimensions(self):
        """Test that TB and LR modes produce different dimensions."""
        gen_tb = FlowchartGenerator(direction="TB")
        gen_lr = FlowchartGenerator(direction="LR")

        input_text = "A -> B\nB -> C\nC -> D"
        result_tb = gen_tb.generate(input_text)
        result_lr = gen_lr.generate(input_text)

        # Get dimensions
        tb_lines = result_tb.split("\n")
        lr_lines = result_lr.split("\n")
        tb_height = len(tb_lines)
        lr_height = len(lr_lines)
        tb_width = max(len(line) for line in tb_lines)
        lr_width = max(len(line) for line in lr_lines)

        # TB should be taller and narrower, LR should be shorter and wider
        assert tb_height > lr_height
        assert lr_width > tb_width

    def test_horizontal_mode_calculate_positions(self):
        """Test calculate_positions_horizontal method."""
        gen = FlowchartGenerator(direction="LR")
        input_text = "A -> B"
        connections = gen.parser.parse(input_text)
        layout_result = gen.layout_engine.layout(connections)
        box_dimensions = gen.position_calculator.calculate_all_box_dimensions(
            layout_result
        )

        positions = gen.position_calculator.calculate_positions_horizontal(
            layout_result, box_dimensions, top_margin=0
        )

        assert "A" in positions
        assert "B" in positions
        # A should be to the left of B
        assert positions["A"][0] < positions["B"][0]

    def test_horizontal_mode_with_margin(self):
        """Test calculate_positions_horizontal with top margin."""
        gen = FlowchartGenerator(direction="LR")
        input_text = "A -> B"
        connections = gen.parser.parse(input_text)
        layout_result = gen.layout_engine.layout(connections)
        box_dimensions = gen.position_calculator.calculate_all_box_dimensions(
            layout_result
        )

        positions_no_margin = gen.position_calculator.calculate_positions_horizontal(
            layout_result, box_dimensions, top_margin=0
        )
        positions_with_margin = gen.position_calculator.calculate_positions_horizontal(
            layout_result, box_dimensions, top_margin=10
        )

        # With margin, y positions should be shifted down
        for name in positions_no_margin:
            assert positions_with_margin[name][1] >= positions_no_margin[name][1]

    def test_calculate_port_y_single(self):
        """Test calculate_port_y for single port."""
        gen = FlowchartGenerator(direction="LR")
        box_y = 10
        box_height = 20

        port_y = gen.position_calculator.calculate_port_y(box_y, box_height, 0, 1)

        # Single port should be centered
        expected_center = box_y + box_height // 2
        assert port_y == expected_center

    def test_calculate_port_y_multiple(self):
        """Test calculate_port_y for multiple ports."""
        gen = FlowchartGenerator(direction="LR")
        box_y = 10
        box_height = 20

        port_y_0 = gen.position_calculator.calculate_port_y(box_y, box_height, 0, 3)
        port_y_1 = gen.position_calculator.calculate_port_y(box_y, box_height, 1, 3)
        port_y_2 = gen.position_calculator.calculate_port_y(box_y, box_height, 2, 3)

        # Ports should be distributed vertically
        assert port_y_0 < port_y_1 < port_y_2

    def test_horizontal_back_edges(self):
        """Test that back edges work in horizontal mode."""
        gen = FlowchartGenerator(direction="LR")
        input_text = """
        Init -> Process
        Process -> Check
        Check -> Process
        Check -> Done
        """
        result = gen.generate(input_text)
        assert "Init" in result
        assert "Process" in result
        assert "Check" in result
        assert "Done" in result

    def test_horizontal_wide_branching(self):
        """Test horizontal mode with wide branching."""
        gen = FlowchartGenerator(direction="LR")
        input_text = """
        Start -> A
        Start -> B
        Start -> C
        Start -> D
        A -> End
        B -> End
        C -> End
        D -> End
        """
        result = gen.generate(input_text)
        assert "Start" in result
        assert "End" in result
        for node in ["A", "B", "C", "D"]:
            assert node in result


class TestFlowchartGeneratorGroupBoxes:
    """Tests for FlowchartGenerator group box feature."""

    def test_group_box_basic(self):
        """Test basic group box generation."""
        gen = FlowchartGenerator()
        input_text = """
        [My Group: A B]
        A -> B
        B -> C
        """
        result = gen.generate(input_text)
        assert "A" in result
        assert "B" in result
        assert "C" in result
        # Group title should appear
        assert "My Group" in result

    def test_group_box_uses_dashed_borders(self):
        """Test that group boxes use dashed border characters."""
        gen = FlowchartGenerator()
        input_text = """
        [Test Group: A B]
        A -> B
        B -> C
        """
        result = gen.generate(input_text)
        # Should contain dashed border characters
        assert DASHED_BOX_CHARS["horizontal"] in result
        assert DASHED_BOX_CHARS["vertical"] in result

    def test_group_box_with_shadow(self):
        """Test that group boxes have shadows."""
        gen = FlowchartGenerator(shadow=True)
        input_text = """
        [Group: A B]
        A -> B
        """
        result = gen.generate(input_text)
        # Shadow character should appear
        assert DASHED_BOX_CHARS["shadow"] in result

    def test_group_box_without_shadow(self):
        """Test group boxes without shadows."""
        gen = FlowchartGenerator(shadow=False)
        input_text = """
        [Group: A B]
        A -> B
        """
        result = gen.generate(input_text)
        # Should still render without error
        assert "A" in result
        assert "B" in result

    def test_multiple_groups(self):
        """Test multiple group boxes in one diagram."""
        gen = FlowchartGenerator()
        input_text = """
        [Group One: A B]
        [Group Two: C D]
        A -> B
        B -> C
        C -> D
        """
        result = gen.generate(input_text)
        assert "Group One" in result
        assert "Group Two" in result
        assert "A" in result
        assert "D" in result

    def test_group_box_with_title(self):
        """Test group box combined with diagram title."""
        gen = FlowchartGenerator(title="My Diagram")
        input_text = """
        [API: Gateway Auth]
        Gateway -> Auth
        Auth -> DB
        """
        result = gen.generate(input_text)
        assert "My Diagram" in result
        assert "API" in result
        assert "Gateway" in result

    def test_group_box_in_lr_mode(self):
        """Test group boxes in left-to-right mode."""
        gen = FlowchartGenerator(direction="LR")
        input_text = """
        [Backend: API DB]
        API -> DB
        DB -> Cache
        """
        result = gen.generate(input_text)
        assert "Backend" in result
        assert "API" in result
        assert "DB" in result
        assert "Cache" in result

    def test_group_box_multiword_nodes(self):
        """Test group boxes with multi-word node names."""
        gen = FlowchartGenerator()
        input_text = """
        [Services: User Service Auth Service]
        User Service -> Auth Service
        Auth Service -> Database
        """
        result = gen.generate(input_text)
        assert "Services" in result
        assert "User Service" in result
        assert "Auth Service" in result

    def test_group_box_preserves_edges(self):
        """Test that edges are drawn correctly with groups."""
        gen = FlowchartGenerator()
        input_text = """
        [Frontend: A B]
        A -> B
        B -> C
        C -> D
        """
        result = gen.generate(input_text)
        # Should have arrow characters
        assert ARROW_CHARS["down"] in result or ARROW_CHARS["right"] in result

    def test_group_box_edges_across_groups(self):
        """Test edges that cross between groups."""
        gen = FlowchartGenerator()
        input_text = """
        [Group1: A B]
        [Group2: C D]
        A -> B
        B -> C
        C -> D
        """
        result = gen.generate(input_text)
        # All nodes should be present
        for node in ["A", "B", "C", "D"]:
            assert node in result
        # Both groups should appear
        assert "Group1" in result
        assert "Group2" in result

    def test_group_with_back_edges(self):
        """Test groups containing nodes involved in cycles."""
        gen = FlowchartGenerator()
        input_text = """
        [Loop: Process Check]
        Init -> Process
        Process -> Check
        Check -> Process
        Check -> Done
        """
        result = gen.generate(input_text)
        assert "Loop" in result
        assert "Init" in result
        assert "Process" in result
        assert "Check" in result
        assert "Done" in result

    def test_group_renderer_initialized(self):
        """Test that group box renderer is initialized."""
        gen = FlowchartGenerator()
        assert gen.group_box_renderer is not None

    def test_debug_mode_with_groups(self):
        """Test debug mode captures group information."""
        gen = FlowchartGenerator()
        input_text = """
        [Test: A B]
        A -> B
        B -> C
        """
        _result = gen.generate(input_text, debug=True)
        trace = gen.get_trace()
        assert trace is not None
        assert _result is not None

        # Parse stage should contain group info
        parse_stage = trace.get_stage("parse")
        assert parse_stage is not None
        assert "groups" in parse_stage.data

    def test_group_box_no_overlap_with_nodes(self):
        """Test that group boxes don't overlap with external nodes."""
        gen = FlowchartGenerator()
        input_text = """
        [Internal: B C]
        A -> B
        B -> C
        C -> D
        """
        result = gen.generate(input_text)
        # Should generate without errors
        assert "A" in result
        assert "B" in result
        assert "C" in result
        assert "D" in result

    def test_group_box_save_png(self):
        """Test saving group box diagram as PNG."""
        gen = FlowchartGenerator()
        input_text = """
        [Services: API DB]
        API -> DB
        DB -> Cache
        """

        with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as f:
            filename = f.name

        try:
            gen.save_png(input_text, filename)
            assert os.path.exists(filename)
            with Image.open(filename) as img:
                assert img.format == "PNG"
        finally:
            if os.path.exists(filename):
                os.remove(filename)

    def test_group_box_save_txt(self):
        """Test saving group box diagram as text file."""
        gen = FlowchartGenerator()
        input_text = """
        [Services: API DB]
        API -> DB
        DB -> Cache
        """

        with tempfile.NamedTemporaryFile(suffix=".txt", delete=False) as f:
            filename = f.name

        try:
            gen.save_txt(input_text, filename)
            with open(filename, "r", encoding="utf-8") as f:
                content = f.read()
            assert "Services" in content
            assert "API" in content
            assert "DB" in content
        finally:
            if os.path.exists(filename):
                os.remove(filename)
