"""Tests for client-side input validation."""

import pytest

from baponi._base import validate_execute_params


class TestCodeValidation:
    def test_empty_code_raises(self):
        with pytest.raises(ValueError, match="must not be empty"):
            validate_execute_params("", "python", 30, None, None)

    def test_oversized_code_raises(self):
        big_code = "x" * (1_048_576 + 1)
        with pytest.raises(ValueError, match="exceeds maximum size"):
            validate_execute_params(big_code, "python", 30, None, None)

    def test_max_size_code_passes(self):
        code = "x" * 1_048_576
        validate_execute_params(code, "python", 30, None, None)


class TestLanguageValidation:
    @pytest.mark.parametrize(
        "lang", ["python", "bash", "node", "ruby", "php", "deno", "bun"]
    )
    def test_valid_languages(self, lang: str):
        validate_execute_params("x", lang, 30, None, None)

    def test_invalid_language_raises(self):
        with pytest.raises(ValueError, match="unsupported language"):
            validate_execute_params("x", "java", 30, None, None)

    def test_case_sensitive(self):
        with pytest.raises(ValueError, match="unsupported language"):
            validate_execute_params("x", "Python", 30, None, None)


class TestTimeoutValidation:
    def test_zero_timeout_raises(self):
        with pytest.raises(ValueError, match="between 1 and 300"):
            validate_execute_params("x", "python", 0, None, None)

    def test_negative_timeout_raises(self):
        with pytest.raises(ValueError, match="between 1 and 300"):
            validate_execute_params("x", "python", -1, None, None)

    def test_over_max_timeout_raises(self):
        with pytest.raises(ValueError, match="between 1 and 300"):
            validate_execute_params("x", "python", 301, None, None)

    def test_boundary_timeouts_pass(self):
        validate_execute_params("x", "python", 1, None, None)
        validate_execute_params("x", "python", 300, None, None)

    def test_non_int_timeout_raises(self):
        with pytest.raises(ValueError, match="must be an integer"):
            validate_execute_params("x", "python", 30.5, None, None)  # type: ignore[arg-type]


class TestThreadIdValidation:
    def test_valid_thread_ids(self):
        validate_execute_params("x", "python", 30, "abc123", None)
        validate_execute_params("x", "python", 30, "my-thread-1", None)
        validate_execute_params("x", "python", 30, "data_analysis_x8k2m9p4z1", None)

    def test_empty_thread_id_raises(self):
        with pytest.raises(ValueError, match="must not be empty"):
            validate_execute_params("x", "python", 30, "", None)

    def test_too_long_thread_id_raises(self):
        with pytest.raises(ValueError, match="exceeds maximum length"):
            validate_execute_params("x", "python", 30, "a" * 129, None)

    def test_max_length_thread_id_passes(self):
        validate_execute_params("x", "python", 30, "a" * 128, None)

    def test_invalid_start_char_raises(self):
        with pytest.raises(ValueError, match="must start with alphanumeric"):
            validate_execute_params("x", "python", 30, "-invalid", None)
        with pytest.raises(ValueError, match="must start with alphanumeric"):
            validate_execute_params("x", "python", 30, "_invalid", None)

    def test_invalid_chars_raises(self):
        with pytest.raises(ValueError, match="must start with alphanumeric"):
            validate_execute_params("x", "python", 30, "has spaces", None)
        with pytest.raises(ValueError, match="must start with alphanumeric"):
            validate_execute_params("x", "python", 30, "has.dots", None)

    def test_none_thread_id_passes(self):
        validate_execute_params("x", "python", 30, None, None)


class TestMetadataValidation:
    def test_valid_metadata(self):
        validate_execute_params("x", "python", 30, None, {"key": "value"})
        validate_execute_params(
            "x", "python", 30, None, {"user.id": "abc", "env-name": "prod"}
        )

    def test_too_many_keys_raises(self):
        meta = {f"key{i}": "val" for i in range(11)}
        with pytest.raises(ValueError, match="exceeds maximum of 10"):
            validate_execute_params("x", "python", 30, None, meta)

    def test_max_keys_passes(self):
        meta = {f"key{i}": "val" for i in range(10)}
        validate_execute_params("x", "python", 30, None, meta)

    def test_key_too_long_raises(self):
        with pytest.raises(ValueError, match="1-40 characters"):
            validate_execute_params("x", "python", 30, None, {"a" * 41: "val"})

    def test_empty_key_raises(self):
        with pytest.raises(ValueError, match="1-40 characters"):
            validate_execute_params("x", "python", 30, None, {"": "val"})

    def test_invalid_key_pattern_raises(self):
        with pytest.raises(ValueError, match="must start with alphanumeric"):
            validate_execute_params("x", "python", 30, None, {"-bad": "val"})
        with pytest.raises(ValueError, match="must start with alphanumeric"):
            validate_execute_params("x", "python", 30, None, {".bad": "val"})

    def test_value_too_long_raises(self):
        with pytest.raises(ValueError, match="exceeds maximum of 256"):
            validate_execute_params("x", "python", 30, None, {"k": "v" * 257})

    def test_non_string_value_raises(self):
        with pytest.raises(ValueError, match="must be a string"):
            validate_execute_params("x", "python", 30, None, {"k": 123})  # type: ignore[dict-item]

    def test_none_metadata_passes(self):
        validate_execute_params("x", "python", 30, None, None)
