"""Tests for the rtkbase adapter CLI subcommand group.

These tests use Click's CliRunner to invoke the `lc29h rtkbase` subcommands
and verify the stdout/stderr/exit-code interface that rtkbase's install.sh
expects.
"""

from unittest.mock import AsyncMock, patch

import pytest
from click.testing import CliRunner

from lc29h.cli import cli
from lc29h.detect import DetectionResult

from .conftest import MockSerialTransport, _fix_checksum

VERNO_BS = _fix_checksum(
    "$PQTMVERNO,LC29HBSNR11A01S,2024/09/15,10:30:00*00"
)


@pytest.fixture
def runner():
    return CliRunner()


@pytest.fixture
def mock_transport():
    transport = MockSerialTransport()
    # Add PQTMVERNO response for BS variant
    transport._responses["$PQTMVERNO"] = VERNO_BS
    return transport


# ===========================================================================
# detect command
# ===========================================================================


class TestDetectCommand:
    def test_successful_detection(self, runner):
        detection = DetectionResult(
            port="/dev/ttyUSB2", baud=115200,
            model="LC29HBS", firmware="LC29HBSNR11A01S",
        )
        with patch("lc29h.rtkbase_adapter.detect_receiver", new_callable=AsyncMock) as mock_det:
            mock_det.return_value = detection
            result = runner.invoke(
                cli, ["--port", "/dev/ttyUSB2", "--baud", "115200", "rtkbase", "detect"]
            )
        assert result.exit_code == 0
        assert "/dev/ttyUSB2" in result.output
        assert "quectel" in result.output
        assert "115200" in result.output
        assert "LC29HBSNR11A01S" in result.output
        assert "LC29HBS" in result.output

    def test_output_format_matches_rtkbase(self, runner):
        """Verify output matches the dash-separated format install.sh parses."""
        detection = DetectionResult(
            port="/dev/ttyUSB2", baud=115200,
            model="LC29HBS", firmware="LC29HBSNR11A01S",
        )
        with patch("lc29h.rtkbase_adapter.detect_receiver", new_callable=AsyncMock) as mock_det:
            mock_det.return_value = detection
            result = runner.invoke(
                cli, ["--port", "/dev/ttyUSB2", "rtkbase", "detect"]
            )
        # install.sh splits on ' - '
        parts = result.output.strip().split(" - ")
        assert len(parts) == 5
        assert parts[0] == "/dev/ttyUSB2"
        assert parts[1] == "quectel"
        assert parts[2] == "115200"
        assert parts[3] == "LC29HBSNR11A01S"
        assert parts[4] == "LC29HBS"

    def test_no_detection_exits_1(self, runner):
        with patch("lc29h.rtkbase_adapter.detect_receiver", new_callable=AsyncMock) as mock_det:
            mock_det.return_value = None
            result = runner.invoke(
                cli, ["--port", "/dev/ttyUSB99", "rtkbase", "detect"]
            )
        assert result.exit_code == 1


# ===========================================================================
# get-model command
# ===========================================================================


class TestGetModelCommand:
    def test_returns_model(self, runner, mock_transport):
        with patch("lc29h.rtkbase_adapter.SerialTransport", return_value=mock_transport):
            result = runner.invoke(
                cli, ["--port", "/dev/ttyUSB2", "rtkbase", "get-model"]
            )
        assert result.exit_code == 0
        assert "LC29HBS" in result.output.strip()

    def test_no_response_exits_1(self, runner):
        transport = MockSerialTransport()
        transport._responses = {}
        with patch("lc29h.rtkbase_adapter.SerialTransport", return_value=transport):
            result = runner.invoke(
                cli, ["--port", "/dev/ttyUSB2", "rtkbase", "get-model"]
            )
        assert result.exit_code == 1


# ===========================================================================
# get-firmware command
# ===========================================================================


class TestGetFirmwareCommand:
    def test_returns_firmware(self, runner, mock_transport):
        with patch("lc29h.rtkbase_adapter.SerialTransport", return_value=mock_transport):
            result = runner.invoke(
                cli, ["--port", "/dev/ttyUSB2", "rtkbase", "get-firmware"]
            )
        assert result.exit_code == 0
        assert "LC29HBSNR11A01S" in result.output.strip()

    def test_no_response_exits_1(self, runner):
        transport = MockSerialTransport()
        transport._responses = {}
        with patch("lc29h.rtkbase_adapter.SerialTransport", return_value=transport):
            result = runner.invoke(
                cli, ["--port", "/dev/ttyUSB2", "rtkbase", "get-firmware"]
            )
        assert result.exit_code == 1


# ===========================================================================
# reset command
# ===========================================================================


class TestResetCommand:
    def test_reset_succeeds(self, runner, mock_transport):
        with patch("lc29h.rtkbase_adapter.SerialTransport", return_value=mock_transport):
            result = runner.invoke(
                cli, ["--port", "/dev/ttyUSB2", "rtkbase", "reset"]
            )
        assert result.exit_code == 0
        # Verify PQTMRESTOREPAR was sent
        written = [w.decode() for w in mock_transport._written]
        assert any("PQTMRESTOREPAR" in w for w in written)


# ===========================================================================
# configure command
# ===========================================================================


class TestConfigureCommand:
    def test_configure_succeeds(self, runner, mock_transport):
        with patch("lc29h.rtkbase_adapter.SerialTransport", return_value=mock_transport):
            result = runner.invoke(
                cli, ["--port", "/dev/ttyUSB2", "rtkbase", "configure"]
            )
        assert result.exit_code == 0

    def test_configure_sends_port_config(self, runner, mock_transport):
        with patch("lc29h.rtkbase_adapter.SerialTransport", return_value=mock_transport):
            runner.invoke(cli, ["--port", "/dev/ttyUSB2", "rtkbase", "configure"])
        written = [w.decode() for w in mock_transport._written]
        # Should have PQTMCFGPORT command
        assert any("PQTMCFGPORT" in w for w in written)

    def test_configure_enables_rtcm_messages(self, runner, mock_transport):
        with patch("lc29h.rtkbase_adapter.SerialTransport", return_value=mock_transport):
            runner.invoke(cli, ["--port", "/dev/ttyUSB2", "rtkbase", "configure"])
        written = [w.decode() for w in mock_transport._written]
        # Should enable RTCM3 messages
        assert any("PQTMCFGMSGRATE" in w for w in written)
        # Check specific messages
        rtcm_cmds = [w for w in written if "PQTMCFGMSGRATE" in w]
        assert len(rtcm_cmds) == 10  # 10 RTCM3 message types

    def test_configure_enables_gga(self, runner, mock_transport):
        with patch("lc29h.rtkbase_adapter.SerialTransport", return_value=mock_transport):
            runner.invoke(cli, ["--port", "/dev/ttyUSB2", "rtkbase", "configure"])
        written = [w.decode() for w in mock_transport._written]
        assert any("PQTMCFGNMEAMSG" in w and "GGA" in w for w in written)

    def test_configure_saves_to_flash(self, runner, mock_transport):
        with patch("lc29h.rtkbase_adapter.SerialTransport", return_value=mock_transport):
            runner.invoke(cli, ["--port", "/dev/ttyUSB2", "rtkbase", "configure"])
        written = [w.decode() for w in mock_transport._written]
        assert any("PQTMSAVEPAR" in w for w in written)

    def test_configure_emits_progress(self, runner, mock_transport):
        """Verify progress messages are emitted during configuration."""
        with patch("lc29h.rtkbase_adapter.SerialTransport", return_value=mock_transport):
            result = runner.invoke(
                cli, ["--port", "/dev/ttyUSB2", "rtkbase", "configure"],
                catch_exceptions=False,
            )
        # CliRunner mixes stderr into output; verify progress content is present
        assert "Configuring UART1" in result.output
        assert "Saving configuration" in result.output
        assert "configuration complete" in result.output


# ===========================================================================
# rtkbase subgroup visibility
# ===========================================================================


class TestRtkbaseSubgroup:
    def test_rtkbase_in_help(self, runner):
        result = runner.invoke(cli, ["--help"])
        assert "rtkbase" in result.output

    def test_rtkbase_subcommands_in_help(self, runner):
        result = runner.invoke(cli, ["rtkbase", "--help"])
        assert result.exit_code == 0
        assert "detect" in result.output
        assert "get-model" in result.output
        assert "get-firmware" in result.output
        assert "reset" in result.output
        assert "configure" in result.output
