import pytest
import networkx as nx
import cogiles


class TestParseErrors:
    def test_empty_string(self):
        with pytest.raises(cogiles.CogilesParseError):
            cogiles.parse("")

    def test_invalid_character(self):
        with pytest.raises(cogiles.CogilesParseError):
            cogiles.parse("RXR")

    def test_unmatched_open_paren(self):
        with pytest.raises(cogiles.CogilesParseError):
            cogiles.parse("RR(BB")

    def test_unmatched_close_paren(self):
        with pytest.raises(cogiles.CogilesParseError):
            cogiles.parse("RR)BB")

    def test_lowercase_node_rejected(self):
        with pytest.raises(cogiles.CogilesParseError):
            cogiles.parse("r")

    def test_error_has_text_attribute(self):
        with pytest.raises(cogiles.CogilesParseError) as exc_info:
            cogiles.parse("RRxRR")
        assert exc_info.value.text == "RRxRR"

    def test_error_has_position_attribute(self):
        with pytest.raises(cogiles.CogilesParseError) as exc_info:
            cogiles.parse("RRxRR")
        assert exc_info.value.position is not None

    def test_error_message_shows_caret(self):
        with pytest.raises(cogiles.CogilesParseError) as exc_info:
            cogiles.parse("RRxRR")
        msg = str(exc_info.value)
        assert "RRxRR" in msg
        assert "^" in msg

    def test_error_is_exception(self):
        with pytest.raises(Exception):
            cogiles.parse("RXR")

    def test_invalid_two_letter_start(self):
        """A lowercase letter not part of a two-letter code should fail."""
        with pytest.raises(cogiles.CogilesParseError):
            cogiles.parse("Rx")

    def test_number_string_rejected(self):
        with pytest.raises(cogiles.CogilesParseError):
            cogiles.parse("123")

    def test_special_characters_rejected(self):
        with pytest.raises(cogiles.CogilesParseError):
            cogiles.parse("R+R")

    def test_leading_branch_rejected(self):
        """A branch at the start of the string with no prior node should error."""
        with pytest.raises(cogiles.CogilesParseError):
            cogiles.parse("(RR)B")

    def test_only_color_name_no_color_raises(self):
        """Node with color_name but no color attribute should raise."""
        G = nx.Graph()
        G.add_node(0, color_name="red")
        with pytest.raises(cogiles.CogilesEncodeError):
            cogiles.encode(G)


class TestEncodeErrors:
    def test_missing_color_attribute(self):
        G = nx.Graph()
        G.add_node(0)
        with pytest.raises(cogiles.CogilesEncodeError):
            cogiles.encode(G)

    def test_strict_unknown_color_has_attributes(self):
        G = nx.Graph()
        G.add_node(0, color=(0.1, 0.2, 0.3))
        with pytest.raises(cogiles.CogilesEncodeError) as exc_info:
            cogiles.encode(G, strict=True)
        assert exc_info.value.color == (0.1, 0.2, 0.3)
        assert exc_info.value.node == 0

    def test_missing_color_error_has_node_attribute(self):
        G = nx.Graph()
        G.add_node(0)
        with pytest.raises(cogiles.CogilesEncodeError) as exc_info:
            cogiles.encode(G)
        assert exc_info.value.node == 0

    def test_encode_error_is_exception(self):
        G = nx.Graph()
        G.add_node(0)
        with pytest.raises(Exception):
            cogiles.encode(G)
