"""Tests for safety guardrails — deny-lists, file validation, limits."""

import pytest

from mcjtag.mixins.raw import _is_dangerous

# ── raw_command deny-list ─────────────────────────────────────────────


class TestDangerousPatterns:
    """Test the _is_dangerous function from raw.py."""

    @pytest.mark.parametrize(
        "command",
        [
            "flash erase_sector 0 0 last",
            "flash write_image firmware.bin",
            "flash protect 0 0 last on",
            "flash fill 0x08000000 0xFF 1024",
            "mww 0x20000000 0xDEADBEEF",
            "mwh 0x20000000 0x1234",
            "mwb 0x20000000 0xFF",
            "write_memory 0x20000000 32 {0xDEADBEEF}",
            "shutdown",
            "exit",
            "reset halt",
            "reset run",
            "reset",
            "reg pc 0x08000000",
            "eval {mww 0x20000000 0xDEADBEEF}",
            "subst {dangerous_cmd}",
            "uplevel 1 {flash erase}",
        ],
    )
    def test_blocks_dangerous_commands(self, command):
        assert _is_dangerous(command) is not None

    @pytest.mark.parametrize(
        "command",
        [
            "version",
            "adapter speed",
            "targets",
            "flash banks",
            "flash info 0",
            "flash list",
            "mdw 0x08000000",
            "mdh 0x08000000",
            "mdb 0x08000000",
            "read_memory 0x08000000 32 1",
            "jtag names",
            "scan_chain",
            "adapter info",
        ],
    )
    def test_allows_safe_commands(self, command):
        assert _is_dangerous(command) is None

    def test_case_insensitive(self):
        assert _is_dangerous("FLASH ERASE_sector 0 0 last") is not None
        assert _is_dangerous("SHUTDOWN") is not None
        assert _is_dangerous("Reset Halt") is not None

    def test_leading_whitespace(self):
        assert _is_dangerous("  shutdown") is not None


# ── flash file validation ─────────────────────────────────────────────


class TestFlashFileValidation:
    """Test the file validation logic extracted from flash_program."""

    ALLOWED_EXTENSIONS = {".bin", ".hex", ".elf", ".ihex", ".srec", ".s19"}

    @pytest.mark.parametrize("ext", [".bin", ".hex", ".elf", ".ihex", ".srec", ".s19"])
    def test_allowed_extensions(self, ext):
        assert ext in self.ALLOWED_EXTENSIONS

    @pytest.mark.parametrize("ext", [".txt", ".py", ".sh", ".exe", ".dll", ".so"])
    def test_rejected_extensions(self, ext):
        assert ext not in self.ALLOWED_EXTENSIONS

    def test_max_flash_image_size(self):
        from mcjtag.mixins.flash import _MAX_FLASH_IMAGE_SIZE

        # 16 MB limit
        assert _MAX_FLASH_IMAGE_SIZE == 16 * 1024 * 1024


# ── memory count/range limits ─────────────────────────────────────────


class TestMemoryLimits:
    def test_max_read_count(self):
        from mcjtag.mixins.memory import _MAX_READ_COUNT

        assert _MAX_READ_COUNT == 4096

    def test_max_search_range_default(self):
        from mcjtag.mixins.memory import _MAX_SEARCH_RANGE

        assert _MAX_SEARCH_RANGE == 1 * 1024 * 1024

    def test_max_search_range_env_override(self, monkeypatch):
        from mcjtag.mixins.memory import _get_max_search_range

        monkeypatch.setenv("MCJTAG_MAX_SEARCH_RANGE", "2097152")
        assert _get_max_search_range() == 2097152

    def test_max_search_range_invalid_env_fallback(self, monkeypatch):
        from mcjtag.mixins.memory import _get_max_search_range

        monkeypatch.setenv("MCJTAG_MAX_SEARCH_RANGE", "not_a_number")
        assert _get_max_search_range() == 1 * 1024 * 1024


# ── SVD file validation constants ─────────────────────────────────────


class TestSVDValidation:
    def test_max_svd_size(self):
        from mcjtag.mixins.svd import _MAX_SVD_SIZE

        assert _MAX_SVD_SIZE == 50 * 1024 * 1024


# ── MCJTAG_ALLOW_RAW env var behavior ─────────────────────────────────


class TestAllowRawEnvVar:
    """Test that the deny-list is gated by the env var (logic from raw.py)."""

    @pytest.mark.parametrize("value", ["true", "1", "yes", "TRUE", "True", "YES"])
    def test_truthy_values(self, value):
        assert value.lower() in ("true", "1", "yes")

    @pytest.mark.parametrize("value", ["false", "0", "no", "", "maybe"])
    def test_falsy_values(self, value):
        assert value.lower() not in ("true", "1", "yes")
