import random
import string
from typing import Any

import psutil

from aitop.core.process.monitor import AIProcessMonitor
from aitop.filtering import SUPPORTED_FIELDS, _parse_query_terms, filter_processes


class _FakeProc:
    def __init__(
        self,
        name_value: Any = "python",
        cmdline_value: Any = None,
        cmdline_error: Exception | None = None,
        name_error: Exception | None = None,
    ) -> None:
        self._name_value = name_value
        self._cmdline_value = cmdline_value
        self._cmdline_error = cmdline_error
        self._name_error = name_error

    def oneshot(self) -> "_FakeProc":
        return self

    def __enter__(self) -> "_FakeProc":
        return self

    def __exit__(self, exc_type: object, exc: object, tb: object) -> bool:
        del exc_type, exc, tb
        return False

    def cmdline(self) -> Any:
        if self._cmdline_error is not None:
            raise self._cmdline_error
        return self._cmdline_value

    def name(self) -> str:
        if self._name_error is not None:
            raise self._name_error
        return str(self._name_value)


def _random_token(rng: random.Random, max_len: int = 24) -> str:
    alphabet = string.ascii_letters + string.digits + " _:-=./[]{}()$%#@!^&*|\t\n"
    length = rng.randint(0, max_len)
    return "".join(rng.choice(alphabet) for _ in range(length))


def test_filtering_fuzz_malformed_queries_do_not_raise() -> None:
    rng = random.Random(20260207)
    processes = [
        {
            "pid": 101,
            "name": "python trainer",
            "username": "alice",
            "cmdline_summary": "python:train.py --epochs 10",
            "status": "running",
            "is_gpu_process": True,
            "gpu_name": "NVIDIA A100",
            "gpu_vendor": "nvidia",
        },
        {
            "pid": 202,
            "name": None,
            "username": 42,
            "cmdline": ["unexpected", "shape"],
            "status": {"state": "sleeping"},
            "is_gpu_process": False,
        },
        {
            "pid": "303",
            "name": "owner:bob-worker",
            "user": "bob",
            "cmdline_summary": "",
            "status": "",
            "gpu": "amd-mi210",
        },
    ]

    for _ in range(400):
        token_count = rng.randint(0, 8)
        query = " ".join(_random_token(rng) for _ in range(token_count))
        results = filter_processes(processes, query)

        assert isinstance(results, list)
        assert all(proc in processes for proc in results)


def test_query_term_parser_fuzz_outputs_well_formed_terms() -> None:
    rng = random.Random(20260301)

    for _ in range(600):
        token_count = rng.randint(0, 12)
        query = " ".join(_random_token(rng) for _ in range(token_count))
        terms = _parse_query_terms(query)

        for term in terms:
            assert term.value != ""
            assert term.field is None or term.field in SUPPORTED_FIELDS


def test_ai_process_monitor_is_ai_process_fuzz_never_raises() -> None:
    rng = random.Random(20260317)
    monitor = AIProcessMonitor()

    weird_names: list[Any] = [
        "",
        None,
        123,
        b"python",
        {"name": "torchrun"},
        ["vllm"],
        object(),
    ]

    weird_cmdlines: list[Any] = [
        "",
        None,
        456,
        b"python -m vllm.entrypoints.openai.api_server",
        {"cmd": "deepspeed train.py"},
        ["python", "train.py"],
        object(),
    ]

    for _ in range(400):
        name = rng.choice(weird_names + [_random_token(rng, max_len=120)])
        cmdline = rng.choice(weird_cmdlines + [_random_token(rng, max_len=240)])
        result = monitor.is_ai_process(name, cmdline)  # type: ignore[arg-type]
        assert isinstance(result, bool)


def test_ai_process_monitor_get_process_cmdline_fuzz_never_raises() -> None:
    rng = random.Random(20260401)
    monitor = AIProcessMonitor()

    cmd_payloads: list[Any] = [
        None,
        [],
        ["python3", "/srv/train.py", "--epochs", "4"],
        ["python3", 123, object()],
        "not-a-list",
        b"\x00binary\xff",
        {"cmd": "python train.py"},
        tuple(["python", "serve.py"]),
    ]

    possible_errors: list[Exception | None] = [
        None,
        RuntimeError("cmdline unavailable"),
        psutil.AccessDenied(pid=99999),
        psutil.NoSuchProcess(pid=99998),
    ]

    for _ in range(300):
        proc = _FakeProc(
            name_value=rng.choice(["python3", "bash", "worker", ""]),
            cmdline_value=rng.choice(cmd_payloads),
            cmdline_error=rng.choice(possible_errors),
            name_error=rng.choice([None, RuntimeError("name unavailable")]),
        )
        cmdline = monitor._get_process_cmdline(proc)  # type: ignore[arg-type]

        assert isinstance(cmdline, str)
        assert len(cmdline) <= 500
