import pytest

plt = pytest.importorskip("matplotlib.pyplot")
matplotlib = pytest.importorskip("matplotlib")

import networkx as nx
import cogiles
from cogiles.visualization import _label_color, LAYOUT_MAP


class TestDrawBasic:
    def test_draw_from_string(self):
        fig, ax = cogiles.draw("RGR")
        assert isinstance(fig, matplotlib.figure.Figure)
        assert isinstance(ax, matplotlib.axes.Axes)
        plt.close(fig)

    def test_draw_from_graph(self):
        G = cogiles.parse("RGR")
        fig, ax = cogiles.draw(G)
        assert isinstance(fig, matplotlib.figure.Figure)
        plt.close(fig)

    def test_draw_single_node(self):
        fig, ax = cogiles.draw("R")
        assert isinstance(fig, matplotlib.figure.Figure)
        plt.close(fig)

    def test_draw_empty_string_raises(self):
        with pytest.raises(cogiles.CogilesParseError):
            cogiles.draw("")


class TestDrawAxesHandling:
    def test_returns_fig_and_ax(self):
        result = cogiles.draw("RG")
        assert isinstance(result, tuple)
        assert len(result) == 2
        plt.close(result[0])

    def test_user_provided_ax(self):
        fig, axes = plt.subplots(1, 2)
        cogiles.draw("RGR", ax=axes[0])
        cogiles.draw("BYB", ax=axes[1])
        # Both axes should have content (children beyond the default)
        assert len(axes[0].collections) > 0 or len(axes[0].patches) > 0
        plt.close(fig)

    def test_user_provided_ax_returns_same_ax(self):
        fig, axes = plt.subplots(1, 2)
        returned_fig, returned_ax = cogiles.draw("RG", ax=axes[0])
        assert returned_ax is axes[0]
        assert returned_fig is fig
        plt.close(fig)

    def test_creates_new_figure_when_no_ax(self):
        fig1, ax1 = cogiles.draw("R")
        fig2, ax2 = cogiles.draw("G")
        assert fig1 is not fig2
        plt.close(fig1)
        plt.close(fig2)


class TestDrawLayouts:
    def test_default_layout_kamada_kawai(self):
        fig, ax = cogiles.draw("RGR")
        plt.close(fig)

    def test_spring_layout(self):
        fig, ax = cogiles.draw("RGR", layout="spring")
        plt.close(fig)

    def test_circular_layout(self):
        fig, ax = cogiles.draw("RGR", layout="circular")
        plt.close(fig)

    def test_invalid_layout_raises(self):
        with pytest.raises(ValueError, match="Unknown layout"):
            cogiles.draw("RGR", layout="nonexistent")

    @pytest.mark.parametrize("layout_name", list(LAYOUT_MAP.keys()))
    def test_all_layouts(self, layout_name):
        fig, ax = cogiles.draw("RGR", layout=layout_name)
        plt.close(fig)


class TestDrawLabels:
    def test_no_labels_default(self):
        fig, ax = cogiles.draw("RGR")
        plt.close(fig)

    def test_symbol_labels(self):
        fig, ax = cogiles.draw("RGR", labels="symbol")
        # Check that text artists were added
        texts = ax.texts
        assert len(texts) > 0
        text_values = sorted(t.get_text() for t in texts)
        assert text_values == ["G", "R", "R"]
        plt.close(fig)

    def test_index_labels(self):
        fig, ax = cogiles.draw("RGR", labels="index")
        texts = ax.texts
        assert len(texts) > 0
        text_values = sorted(t.get_text() for t in texts)
        assert text_values == ["0", "1", "2"]
        plt.close(fig)

    def test_invalid_labels_raises(self):
        with pytest.raises(ValueError, match="Unknown labels"):
            cogiles.draw("RGR", labels="invalid")


class TestDrawNodeAppearance:
    def test_node_colors_match_graph(self):
        fig, ax = cogiles.draw("RGB")
        # nx.draw_networkx_nodes adds a PathCollection to ax.collections
        assert len(ax.collections) >= 1
        node_collection = ax.collections[0]
        face_colors = node_collection.get_facecolors()
        assert len(face_colors) == 3
        plt.close(fig)

    def test_node_outlines_present(self):
        fig, ax = cogiles.draw("RGB")
        node_collection = ax.collections[0]
        edge_colors = node_collection.get_edgecolors()
        assert len(edge_colors) > 0
        linewidths = node_collection.get_linewidths()
        assert all(lw > 0 for lw in linewidths)
        plt.close(fig)

    def test_custom_node_size(self):
        fig, ax = cogiles.draw("R", node_size=500)
        assert isinstance(fig, matplotlib.figure.Figure)
        plt.close(fig)

    def test_black_node_has_outline(self):
        fig, ax = cogiles.draw("K")
        node_collection = ax.collections[0]
        linewidths = node_collection.get_linewidths()
        assert all(lw > 0 for lw in linewidths)
        plt.close(fig)

    def test_white_node_has_outline(self):
        fig, ax = cogiles.draw("W")
        node_collection = ax.collections[0]
        linewidths = node_collection.get_linewidths()
        assert all(lw > 0 for lw in linewidths)
        plt.close(fig)


class TestDrawEdges:
    def test_edges_drawn(self):
        fig, ax = cogiles.draw("RGR")
        # Edges appear as LineCollection or FancyArrowPatch
        # For undirected graphs, nx adds a LineCollection
        assert len(ax.collections) >= 1
        plt.close(fig)

    def test_custom_edge_color(self):
        fig, ax = cogiles.draw("RGR", edge_color="gray")
        assert isinstance(fig, matplotlib.figure.Figure)
        plt.close(fig)

    def test_disconnected_no_edges_between_components(self):
        G = cogiles.parse("R.B")
        fig, ax = cogiles.draw(G)
        assert G.number_of_edges() == 0
        plt.close(fig)


class TestDrawComplex:
    def test_cycle_graph(self):
        fig, ax = cogiles.draw("R-1RRRRR-1")
        plt.close(fig)

    def test_branched_graph(self):
        fig, ax = cogiles.draw("Y(G)(G)(G)")
        plt.close(fig)

    def test_disconnected_graph(self):
        fig, ax = cogiles.draw("RR.BB")
        plt.close(fig)

    def test_two_letter_symbols(self):
        fig, ax = cogiles.draw("BrGrPi")
        plt.close(fig)

    def test_complex_with_anchors_and_branches(self):
        fig, ax = cogiles.draw("R-1R(GY-1)BRRR(R)RB")
        plt.close(fig)

    def test_draw_with_all_options(self):
        fig, ax = cogiles.draw(
            "RGR",
            layout="circular",
            labels="symbol",
            node_size=500,
            edge_color="gray",
            node_edge_color="red",
            node_edge_width=2.0,
        )
        plt.close(fig)


class TestLabelContrast:
    def test_dark_node_gets_white_label(self):
        assert _label_color((0.0, 0.0, 0.0)) == "white"

    def test_light_node_gets_black_label(self):
        assert _label_color((1.0, 1.0, 1.0)) == "black"

    def test_red_gets_white_label(self):
        # Red: luminance = 0.299 * 1.0 = 0.299 < 0.5
        assert _label_color((1.0, 0.0, 0.0)) == "white"

    def test_yellow_gets_black_label(self):
        # Yellow: luminance = 0.299 * 1.0 + 0.587 * 1.0 = 0.886 > 0.5
        assert _label_color((1.0, 1.0, 0.0)) == "black"

    def test_blue_gets_white_label(self):
        # Blue: luminance = 0.114 * 1.0 = 0.114 < 0.5
        assert _label_color((0.0, 0.0, 1.0)) == "white"

    def test_cyan_gets_black_label(self):
        # Cyan: luminance = 0.587 * 1.0 + 0.114 * 1.0 = 0.701 > 0.5
        assert _label_color((0.0, 1.0, 1.0)) == "black"
