"""Tests for firmware CLI subcommands.

Tests the Click CLI interface for firmware operations using mocked
transport to avoid hardware dependency.
"""

from unittest.mock import AsyncMock, MagicMock, patch

import pytest
from click.testing import CliRunner

from lc29h.cli import cli


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


class TestFirmwareCli:
    def test_firmware_group_exists(self, runner):
        """The firmware subcommand group should be registered."""
        result = runner.invoke(cli, ["firmware", "--help"])
        assert result.exit_code == 0
        assert "firmware" in result.output.lower()
        assert "info" in result.output
        assert "backup" in result.output
        assert "update" in result.output
        assert "verify" in result.output

    def test_firmware_info(self, runner):
        """firmware info should query NMEA version info."""
        with patch("lc29h.cli.SerialTransport") as mock_cls:
            mock_transport = MagicMock()
            mock_transport.connect = AsyncMock()
            mock_transport.disconnect = AsyncMock()
            mock_transport.query = AsyncMock(side_effect=[
                "$PAIR021,AG3335A_V2.6.0*00",
                "$PQTMVERNO,LC29HDANR11A03S_RSA,2024/03/19,13:55:31*00",
            ])
            mock_cls.return_value = mock_transport

            runner.invoke(cli, ["--port", "/dev/null", "firmware", "info"])
            # Should attempt to query version info
            assert mock_transport.query.call_count == 2

    def test_firmware_update_requires_yes(self, runner):
        """firmware update without --yes should prompt for confirmation."""
        import os
        import tempfile

        with tempfile.NamedTemporaryFile(suffix=".bin", delete=False) as f:
            f.write(b"\x00" * 4096)
            f.flush()
            tmp_path = f.name

        try:
            result = runner.invoke(
                cli,
                ["--port", "/dev/null", "firmware", "update", tmp_path],
                input="n\n",  # Answer "no" to confirmation
            )
            # Should exit with code 4 (user abort) or prompt
            assert result.exit_code != 0
        finally:
            os.unlink(tmp_path)

    def test_firmware_update_missing_file(self, runner):
        """firmware update with nonexistent file should fail."""
        result = runner.invoke(
            cli,
            ["--port", "/dev/null", "firmware", "update", "/nonexistent/fw.bin", "--yes"],
        )
        # Click should report the file doesn't exist (exit code 2 from Click)
        assert result.exit_code != 0

    def test_firmware_verify_missing_file(self, runner):
        """firmware verify with nonexistent file should fail."""
        result = runner.invoke(
            cli,
            ["--port", "/dev/null", "firmware", "verify", "/nonexistent/fw.bin"],
        )
        assert result.exit_code != 0


class TestFirmwareCliExitCodes:
    """Test that CLI exit codes match the specification."""

    def test_help_exits_zero(self, runner):
        result = runner.invoke(cli, ["firmware", "--help"])
        assert result.exit_code == 0

    def test_info_help(self, runner):
        result = runner.invoke(cli, ["firmware", "info", "--help"])
        assert result.exit_code == 0

    def test_backup_help(self, runner):
        result = runner.invoke(cli, ["firmware", "backup", "--help"])
        assert result.exit_code == 0

    def test_update_help(self, runner):
        result = runner.invoke(cli, ["firmware", "update", "--help"])
        assert result.exit_code == 0

    def test_verify_help(self, runner):
        result = runner.invoke(cli, ["firmware", "verify", "--help"])
        assert result.exit_code == 0
