from __future__ import annotations

import asyncio
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path

from cmvr_edge_ai.application import (
    ApplicationState,
    EdgeAIApplication,
    create_default_registry,
    validate_application,
)
from cmvr_edge_ai.cli import main
from cmvr_edge_ai.config import load_config


PROJECT_ROOT = Path(__file__).resolve().parents[2]
MINIMAL_CONFIG = PROJECT_ROOT / "tests" / "fixtures" / "minimal_pipeline.yaml"
EDGE_AI_CONFIG = PROJECT_ROOT / "configs" / "edge_ai.yaml"


def test_application_runs_the_finite_minimal_pipeline() -> None:
    async def exercise():  # type: ignore[no-untyped-def]
        loop = asyncio.get_running_loop()
        host_executor = ThreadPoolExecutor(max_workers=1)
        loop.set_default_executor(host_executor)
        application = EdgeAIApplication(
            load_config(MINIMAL_CONFIG),
            create_default_registry(discover_entry_points=False),
        )
        try:
            await application.start()
            running_health = await application.health()
            await application.wait()
            await application.stop()
            # The application owns only its bounded executor. Stopping it must
            # not poison an embedding event loop's pre-existing executor.
            host_executor_result = await asyncio.to_thread(lambda: "still-usable")
            return application, running_health, host_executor_result
        finally:
            # Python 3.12 Runner shuts its default executor down from another
            # helper thread. Explicit cleanup keeps this regression deterministic
            # in restricted containers where that helper cannot wake the loop.
            host_executor.shutdown(wait=True, cancel_futures=True)
            loop._default_executor = None  # type: ignore[attr-defined]

    application, running_health, host_executor_result = asyncio.run(exercise())

    assert running_health["state"] == "running"
    assert set(running_health["pipelines"]) == {"minimal"}
    assert application.pipelines == ("minimal",)
    assert application.state is ApplicationState.STOPPED
    assert host_executor_result == "still-usable"


def test_cli_validates_and_runs_minimal_config(
    capsys, monkeypatch
) -> None:  # type: ignore[no-untyped-def]
    # ``configure_logging(force=True)`` intentionally owns process logging in
    # production. Avoid leaking that global CLI side effect into later tests.
    monkeypatch.setattr("cmvr_edge_ai.cli.configure_logging", lambda *_: None)
    assert main(["validate", "--config", str(MINIMAL_CONFIG)]) == 0
    validation_output = capsys.readouterr()
    assert "configuration is valid; pipelines: minimal" in validation_output.out
    assert validation_output.err == ""

    assert (
        main(
            [
                "run",
                "--config",
                str(MINIMAL_CONFIG),
                "--log-level",
                "WARNING",
            ]
        )
        == 0
    )
    run_output = capsys.readouterr()
    assert run_output.err == ""


def test_cli_lists_versioned_builtin_plugins(capsys) -> None:  # type: ignore[no-untyped-def]
    assert main(["plugins"]) == 0

    output = capsys.readouterr().out
    assert "core.sequence_source@1\tsource" in output
    assert "detection.model@1\toperator" in output
    assert "media.video_decoder.pyav@1\toperator" in output
    assert "safety.robot_command_gate@1\toperator" in output


def test_cli_lists_detection_model_metadata(capsys) -> None:  # type: ignore[no-untyped-def]
    assert main(["models"]) == 0

    output = capsys.readouterr().out
    assert (
        "construction-ppe-yolov8@1\tConstruction PPE YOLOv8s\t" "ultralytics-yolo"
    ) in output
    assert "No-Helmet" in output
    assert "No-Vest" in output
    assert (
        "ppe-6classes-yolov8n@1\tPPE Detection YOLOv8n (6 Classes)\t"
        "ultralytics-yolo"
    ) in output
    assert "Gloves,Vest,goggles,helmet,mask,safety_shoe" in output


def test_merged_config_compiles_each_pipeline_without_loading_optional_runtimes() -> None:
    config = load_config(EDGE_AI_CONFIG)
    registry = create_default_registry(discover_entry_points=False)
    talk = validate_application(config, registry, ("talk",))
    compiled = validate_application(
        config,
        registry,
        ("detection",),
    )

    assert set(config.pipelines) == {"detection", "talk"}
    assert tuple(item.pipeline_id for item in talk) == ("talk",)
    assert len(compiled) == 1
    assert compiled[0].pipeline_id == "detection"
    assert "ppe_alert_platform" in config.endpoints
    assert (
        config.pipelines["detection"]
        .nodes["alert_platform"]
        .params["endpoint"]
        == "ppe_alert_platform"
    )
    assert (
        config.pipelines["detection"]
        .nodes["alert_platform"]
        .params["failure_mode"]
        == "log_and_drop"
    )
    assert set(compiled[0].plugin_specs) == {
        "camera",
        "decoder",
        "detector",
        "repeat_gate",
        "alert_platform",
    }
