"""Mock validation executor for testing pipelines.

This class belongs in ``features/mocks/``, not ``features/steps/``, per
the mock placement rule documented in the CONTRIBUTING guide.
"""

from __future__ import annotations

import sys
import time
from typing import Any, cast


class MockValidationExecutor:
    """Configurable mock executor for testing the validation pipeline."""

    def __init__(self) -> None:
        self._results: dict[str, dict[str, Any]] = {}
        self._timeout_names: set[str] = set()
        self._exception_names: dict[str, Exception] = {}
        self._non_dict_names: set[str] = set()
        self._missing_passed_names: set[str] = set()
        self._default_passed: bool = False
        self._stdout_names: set[str] = set()
        self._stderr_names: set[str] = set()

    def set_passed(self, name: str) -> None:
        self._results[name] = {"passed": True, "message": f"{name} passed"}

    def set_passed_with_data(self, name: str) -> None:
        self._results[name] = {
            "passed": True,
            "message": f"{name} passed with data",
            "data": {"detail": "some-value", "count": 42},
        }

    def set_failed(self, name: str, message: str) -> None:
        self._results[name] = {"passed": False, "message": message}

    def set_timeout(self, name: str) -> None:
        self._timeout_names.add(name)

    def set_exception(self, name: str, exc: Exception) -> None:
        self._exception_names[name] = exc

    def set_non_dict(self, name: str) -> None:
        self._non_dict_names.add(name)

    def set_missing_passed(self, name: str) -> None:
        self._missing_passed_names.add(name)

    def set_default_passed(self) -> None:
        self._default_passed = True

    def set_non_bool_passed(self, name: str) -> None:
        self._results[name] = {"passed": "yes", "message": f"{name} passed"}

    def set_non_string_message(self, name: str) -> None:
        self._results[name] = {"passed": True, "message": 12345}

    def set_non_dict_data(self, name: str) -> None:
        self._results[name] = {
            "passed": True,
            "message": f"{name} ok",
            "data": [1, 2],
        }

    def set_prints_stdout(self, name: str) -> None:
        self._results[name] = {"passed": True, "message": f"{name} ok"}
        self._stdout_names.add(name)

    def set_prints_stderr(self, name: str) -> None:
        self._results[name] = {"passed": True, "message": f"{name} ok"}
        self._stderr_names.add(name)

    def __call__(
        self, validation_name: str, arguments: dict[str, Any]
    ) -> dict[str, Any]:
        if validation_name in self._timeout_names:
            # Sleep longer than the test timeout (0.2 s) but not excessively.
            _real_sleep = getattr(time, "_original_sleep", time.sleep)
            _real_sleep(1)
            return {"passed": True, "message": "should not reach here"}

        if validation_name in self._exception_names:
            raise self._exception_names[validation_name]

        if validation_name in self._non_dict_names:
            # Cast needed because the mock intentionally returns non-dict
            # output to exercise _normalise_executor_output normalisation.
            return cast(dict[str, Any], "not-a-dict")

        if validation_name in self._missing_passed_names:
            return {"message": "no passed key here"}

        if validation_name in self._stdout_names:
            print(f"stdout from {validation_name}")

        if validation_name in self._stderr_names:
            print(f"stderr from {validation_name}", file=sys.stderr)

        if validation_name in self._results:
            return self._results[validation_name]

        if self._default_passed:
            return {
                "passed": True,
                "message": f"{validation_name} passed (default)",
            }

        return {
            "passed": False,
            "message": f"{validation_name}: no result configured",
        }
