"""Ultralytics adapter tests — format mapping, mock YOLO dispatch, and error handling."""

from __future__ import annotations

import pytest

from matrice_export import ExportPipeline
from matrice_export.adapters.ultralytics import UltralyticsAdapter

from .conftest import MockYOLO

# ------------------------------------------------------------------ #
# 1. FORMAT_MAP has all expected formats
# ------------------------------------------------------------------ #

class TestFormatMap:
    def test_format_map_has_expected_keys(self):
        """UltralyticsAdapter.FORMAT_MAP contains the core export formats."""
        expected = {"onnx", "torchscript", "openvino", "engine", "coreml", "tflite"}
        actual = set(UltralyticsAdapter.FORMAT_MAP.keys())
        assert expected.issubset(actual), f"Missing keys: {expected - actual}"

    def test_format_map_is_non_empty(self):
        """FORMAT_MAP is a non-empty dict."""
        assert len(UltralyticsAdapter.FORMAT_MAP) > 0

    def test_format_map_values_are_strings(self):
        """Every value in FORMAT_MAP is a string."""
        for key, val in UltralyticsAdapter.FORMAT_MAP.items():
            assert isinstance(val, str), f"FORMAT_MAP[{key!r}] is {type(val).__name__}, expected str"


# ------------------------------------------------------------------ #
# 2. MockYOLO is detected as ultralytics type
# ------------------------------------------------------------------ #

class TestMockYOLODetection:
    def test_mock_yolo_is_ultralytics(self):
        """MockYOLO (with .export and .predict) is classified as 'ultralytics'."""
        mock = MockYOLO()
        pipeline = ExportPipeline(mock)
        assert pipeline.model_type == "ultralytics"


# ------------------------------------------------------------------ #
# 3. Pipeline with MockYOLO does not crash
# ------------------------------------------------------------------ #

class TestMockYOLOExport:
    def test_mock_yolo_pipeline_does_not_crash(self, tmp_path):
        """Calling export on a MockYOLO captures any errors gracefully (no exception raised)."""
        mock = MockYOLO(export_path=str(tmp_path / "mock.onnx"))
        pipeline = ExportPipeline(mock, task="detection")
        # MockYOLO.export() returns a path but the file won't actually exist,
        # so the adapter's _collect_artefact may fail. The pipeline should
        # capture this and return status='error' rather than raise.
        results = pipeline.export(["onnx"], str(tmp_path), validate=False)
        assert "onnx" in results
        # The status is either success (if the mock path worked) or error (if not).
        assert results["onnx"]["status"] in ("success", "error")

    def test_mock_yolo_export_multi_format(self, tmp_path):
        """Requesting multiple formats from MockYOLO returns results for each."""
        mock = MockYOLO(export_path=str(tmp_path / "mock.onnx"))
        pipeline = ExportPipeline(mock, task="detection")
        results = pipeline.export(["onnx", "torchscript"], str(tmp_path), validate=False)
        assert len(results) == 2


# ------------------------------------------------------------------ #
# 4. Format mapping works correctly
# ------------------------------------------------------------------ #

class TestFormatMapping:
    def test_onnx_maps_to_onnx(self):
        """The canonical name 'onnx' maps to the Ultralytics string 'onnx'."""
        assert UltralyticsAdapter.FORMAT_MAP["onnx"] == "onnx"

    def test_torchscript_maps_to_torchscript(self):
        """The canonical name 'torchscript' maps to the Ultralytics string 'torchscript'."""
        assert UltralyticsAdapter.FORMAT_MAP["torchscript"] == "torchscript"

    def test_engine_maps_to_engine(self):
        """The canonical name 'engine' maps to the Ultralytics string 'engine'."""
        assert UltralyticsAdapter.FORMAT_MAP["engine"] == "engine"


# ------------------------------------------------------------------ #
# 5. Unsupported format raises ValueError
# ------------------------------------------------------------------ #

class TestUnsupportedFormat:
    def test_unsupported_format_raises_value_error(self):
        """Requesting an unknown format from the adapter raises ValueError."""
        adapter = UltralyticsAdapter()
        mock = MockYOLO()
        with pytest.raises(ValueError, match="Unsupported Ultralytics format"):
            adapter.export(mock, "totally_fake_format_xyz")

    def test_unsupported_format_error_lists_supported(self):
        """The ValueError message mentions the supported formats."""
        adapter = UltralyticsAdapter()
        mock = MockYOLO()
        with pytest.raises(ValueError, match="Supported"):
            adapter.export(mock, "totally_fake_format_xyz")
