"""
Tests for the add_schematic_text tool.

Covers:
  - WireManager.add_text S-expression insertion
  - Correct position, angle, font options, justification
  - String escaping (double quotes in text)
  - Parameter validation in _handle_add_schematic_text
"""

import sys
import textwrap
from pathlib import Path
from typing import Any
from unittest.mock import patch

import pytest
import sexpdata

sys.path.insert(0, str(Path(__file__).parent.parent / "python"))


# ---------------------------------------------------------------------------
# Shared helpers
# ---------------------------------------------------------------------------


def _make_iface() -> Any:
    with patch("kicad_interface.USE_IPC_BACKEND", False):
        from kicad_interface import KiCADInterface

        iface = KiCADInterface.__new__(KiCADInterface)
    return iface


@pytest.fixture()
def iface():
    return _make_iface()


_MINIMAL_SCH = textwrap.dedent("""\
    (kicad_sch (version 20250114) (generator "test")
    \t(uuid aaaaaaaa-0000-0000-0000-000000000001)
    \t(paper "A4")
    \t(sheet_instances (path "/" (page "1")))
    )
    """)


# ---------------------------------------------------------------------------
# WireManager.add_text unit tests
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestWireManagerAddText:
    def test_inserts_text_element(self, tmp_path):
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")

        result = WireManager.add_text(sch, "Test Note", [50.0, 40.0])

        assert result is True
        content = sch.read_text(encoding="utf-8")
        assert '(text "Test Note"' in content
        assert "(at 50.0 40.0 0)" in content

    def test_inserts_before_sheet_instances(self, tmp_path):
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")

        WireManager.add_text(sch, "Section A", [10.0, 20.0])

        content = sch.read_text(encoding="utf-8")
        text_pos = content.find('(text "Section A"')
        instances_pos = content.find("(sheet_instances")
        assert text_pos < instances_pos

    def test_rotation_angle(self, tmp_path):
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")

        WireManager.add_text(sch, "Rotated", [20.0, 20.0], angle=90)

        content = sch.read_text(encoding="utf-8")
        assert "(at 20.0 20.0 90)" in content

    def test_bold_and_italic_flags(self, tmp_path):
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")

        WireManager.add_text(sch, "Bold Italic", [30.0, 30.0], bold=True, italic=True)

        content = sch.read_text(encoding="utf-8")
        assert "(bold yes)" in content
        assert "(italic yes)" in content

    def test_center_justification(self, tmp_path):
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")

        WireManager.add_text(sch, "Centered", [50.0, 50.0], justify="center")

        content = sch.read_text(encoding="utf-8")
        assert "(justify center bottom)" in content

    def test_custom_font_size(self, tmp_path):
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")

        WireManager.add_text(sch, "Big Text", [10.0, 10.0], font_size=2.54)

        content = sch.read_text(encoding="utf-8")
        assert "(size 2.54 2.54)" in content

    def test_escapes_double_quotes_in_text(self, tmp_path):
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")

        WireManager.add_text(sch, 'He said "hello"', [10.0, 10.0])

        content = sch.read_text(encoding="utf-8")
        assert r"He said \"hello\"" in content

    def test_escapes_newlines_in_multiline_text(self, tmp_path):
        """Raw newlines in quoted string literals break kicad-cli's parser
        (silently in eeschema, but kicad-cli sch reports 'Failed to load
        schematic'). They must be escaped to the two-character \\n sequence."""
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")

        WireManager.add_text(sch, "line one\nline two\r\nline three", [10.0, 10.0])

        content = sch.read_text(encoding="utf-8")
        # The text S-expression's quoted argument must not contain a literal
        # newline. Find the (text "...") and check its first quoted arg.
        text_start = content.index('(text "') + len('(text "')
        # Find matching close-quote, respecting backslash escapes.
        i = text_start
        while i < len(content):
            if content[i] == "\\":
                i += 2
                continue
            if content[i] == '"':
                break
            i += 1
        quoted = content[text_start:i]
        assert (
            "\n" not in quoted and "\r" not in quoted
        ), f"Quoted text still contains a raw newline: {quoted!r}"
        assert "\\n" in quoted, "Newline should be escaped to \\n"

        # And the file must round-trip through the s-expression parser cleanly.
        sexpdata.loads(content)

    def test_result_is_valid_sexp(self, tmp_path):
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")

        WireManager.add_text(sch, "Note", [10.0, 10.0])

        # Must parse without error
        sexpdata.loads(sch.read_text(encoding="utf-8"))

    def test_no_bold_italic_by_default(self, tmp_path):
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")

        WireManager.add_text(sch, "Plain", [10.0, 10.0])

        content = sch.read_text(encoding="utf-8")
        assert "(bold yes)" not in content
        assert "(italic yes)" not in content


# ---------------------------------------------------------------------------
# _handle_add_schematic_text parameter-validation tests
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestHandleAddSchematicText:
    def test_missing_schematic_path(self, iface):
        result = iface._handle_add_schematic_text({"text": "Note", "position": [10.0, 20.0]})
        assert result["success"] is False
        assert "schematicPath" in result["message"]

    def test_missing_text(self, iface, tmp_path):
        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")
        result = iface._handle_add_schematic_text(
            {"schematicPath": str(sch), "position": [10.0, 20.0]}
        )
        assert result["success"] is False
        assert "text" in result["message"]

    def test_missing_position(self, iface, tmp_path):
        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")
        result = iface._handle_add_schematic_text({"schematicPath": str(sch), "text": "Note"})
        assert result["success"] is False
        assert "position" in result["message"]

    def test_invalid_justify(self, iface, tmp_path):
        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")
        result = iface._handle_add_schematic_text(
            {
                "schematicPath": str(sch),
                "text": "Note",
                "position": [10.0, 20.0],
                "justify": "top",
            }
        )
        assert result["success"] is False
        assert "justify" in result["message"]

    def test_invalid_font_size(self, iface, tmp_path):
        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")
        result = iface._handle_add_schematic_text(
            {
                "schematicPath": str(sch),
                "text": "Note",
                "position": [10.0, 20.0],
                "fontSize": 0,
            }
        )
        assert result["success"] is False
        assert "fontSize" in result["message"]

    def test_nonexistent_file(self, iface, tmp_path):
        result = iface._handle_add_schematic_text(
            {
                "schematicPath": str(tmp_path / "nope.kicad_sch"),
                "text": "Note",
                "position": [10, 20],
            }
        )
        assert result["success"] is False
        assert "not found" in result["message"].lower()

    def test_success(self, iface, tmp_path):
        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")
        result = iface._handle_add_schematic_text(
            {
                "schematicPath": str(sch),
                "text": "Power Section",
                "position": [25.4, 50.8],
                "angle": 0,
                "fontSize": 1.5,
            }
        )
        assert result["success"] is True
        assert "position" in result
        content = sch.read_text(encoding="utf-8")
        assert '(text "Power Section"' in content

    def test_success_returns_position(self, iface, tmp_path):
        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")
        result = iface._handle_add_schematic_text(
            {"schematicPath": str(sch), "text": "Note", "position": [12.7, 25.4]}
        )
        assert result["success"] is True
        assert result["position"] == {"x": 12.7, "y": 25.4}


# ---------------------------------------------------------------------------
# WireManager.list_texts unit tests
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestWireManagerListTexts:
    def test_empty_schematic_returns_empty_list(self, tmp_path):
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")

        result = WireManager.list_texts(sch)

        assert result == []

    def test_lists_added_text(self, tmp_path):
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")
        WireManager.add_text(sch, "Power Section", [10.0, 20.0])

        result = WireManager.list_texts(sch)

        assert len(result) == 1
        assert result[0]["text"] == "Power Section"
        assert result[0]["position"] == {"x": 10.0, "y": 20.0}

    def test_lists_multiple_texts(self, tmp_path):
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")
        WireManager.add_text(sch, "Alpha", [0.0, 0.0])
        WireManager.add_text(sch, "Beta", [10.0, 10.0])

        result = WireManager.list_texts(sch)

        texts = {r["text"] for r in result}
        assert texts == {"Alpha", "Beta"}

    def test_angle_is_preserved(self, tmp_path):
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")
        WireManager.add_text(sch, "Rotated", [5.0, 5.0], angle=90)

        result = WireManager.list_texts(sch)

        assert result[0]["angle"] == 90.0

    def test_bold_italic_are_preserved(self, tmp_path):
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")
        WireManager.add_text(sch, "Styled", [5.0, 5.0], bold=True, italic=True)

        result = WireManager.list_texts(sch)

        assert result[0]["bold"] is True
        assert result[0]["italic"] is True

    def test_font_size_is_preserved(self, tmp_path):
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")
        WireManager.add_text(sch, "Big", [5.0, 5.0], font_size=2.54)

        result = WireManager.list_texts(sch)

        assert result[0]["font_size"] == 2.54

    def test_nonexistent_file_returns_none(self, tmp_path):
        from commands.wire_manager import WireManager

        result = WireManager.list_texts(tmp_path / "nope.kicad_sch")

        assert result is None


# ---------------------------------------------------------------------------
# _handle_list_schematic_texts handler tests
# ---------------------------------------------------------------------------


@pytest.mark.unit
class TestHandleListSchematicTexts:
    def test_missing_schematic_path(self, iface):
        result = iface._handle_list_schematic_texts({})
        assert result["success"] is False
        assert "schematicPath" in result["message"]

    def test_nonexistent_file(self, iface, tmp_path):
        result = iface._handle_list_schematic_texts(
            {"schematicPath": str(tmp_path / "nope.kicad_sch")}
        )
        assert result["success"] is False
        assert "not found" in result["message"].lower()

    def test_empty_schematic(self, iface, tmp_path):
        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")
        result = iface._handle_list_schematic_texts({"schematicPath": str(sch)})
        assert result["success"] is True
        assert result["texts"] == []
        assert result["count"] == 0

    def test_returns_all_texts(self, iface, tmp_path):
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")
        WireManager.add_text(sch, "Note A", [0.0, 0.0])
        WireManager.add_text(sch, "Note B", [10.0, 10.0])

        result = iface._handle_list_schematic_texts({"schematicPath": str(sch)})

        assert result["success"] is True
        assert result["count"] == 2

    def test_text_filter_substring_match(self, iface, tmp_path):
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")
        WireManager.add_text(sch, "Power Supply", [0.0, 0.0])
        WireManager.add_text(sch, "Ground Plane", [10.0, 10.0])

        result = iface._handle_list_schematic_texts({"schematicPath": str(sch), "text": "power"})

        assert result["success"] is True
        assert result["count"] == 1
        assert result["texts"][0]["text"] == "Power Supply"

    def test_text_filter_case_insensitive(self, iface, tmp_path):
        from commands.wire_manager import WireManager

        sch = tmp_path / "test.kicad_sch"
        sch.write_text(_MINIMAL_SCH, encoding="utf-8")
        WireManager.add_text(sch, "SECTION HEADER", [0.0, 0.0])

        result = iface._handle_list_schematic_texts({"schematicPath": str(sch), "text": "section"})

        assert result["success"] is True
        assert result["count"] == 1
