"""Task-resolution path of `Model.from_pretrained`.

Three paths, exercised via the module-level `_resolve_task` helper:

1. `config.json` carries `acaua_task` → that wins.
2. Missing field, repo_id in `TASK_FALLBACK` → fallback.
3. Missing field, repo not in fallback → `TaskResolutionError`.

Plus `WeightDownloadError` when HF returns a 4xx/5xx or the cached file is
corrupt.
"""

from __future__ import annotations

import json
from pathlib import Path
from unittest.mock import patch

import pytest
from huggingface_hub.errors import HfHubHTTPError

from acaua import TaskResolutionError, WeightDownloadError
from acaua._data.licenses import LICENSES
from acaua._data.task_fallback import TASK_FALLBACK
from acaua.model import _load_adapter, _resolve_task


def _write_config(tmp_path: Path, payload: dict[str, object]) -> str:
    path = tmp_path / "config.json"
    path.write_text(json.dumps(payload))
    return str(path)


class TestResolveTaskHappyPaths:
    def test_metadata_field_wins(self, tmp_path: Path) -> None:
        cfg_path = _write_config(tmp_path, {"acaua_task": "detection", "model_type": "rtdetr"})
        with patch("huggingface_hub.hf_hub_download", return_value=cfg_path):
            task = _resolve_task("CondadosAI/acaua-task-test", revision="deadbeef")
        assert task == "detection"

    def test_metadata_wins_over_fallback(self, tmp_path: Path) -> None:
        """If config has `acaua_task`, the fallback dict is NOT consulted.

        Constructed case: a repo that IS in TASK_FALLBACK (would route to
        segmentation) but whose config.json says detection. Metadata wins.
        """
        cfg_path = _write_config(tmp_path, {"acaua_task": "detection"})
        with patch("huggingface_hub.hf_hub_download", return_value=cfg_path):
            task = _resolve_task("CondadosAI/mask2former_swin_tiny_coco_instance", revision="x")
        assert task == "detection"

    def test_fallback_used_when_metadata_missing(self, tmp_path: Path) -> None:
        cfg_path = _write_config(tmp_path, {"model_type": "rtdetr"})
        with patch("huggingface_hub.hf_hub_download", return_value=cfg_path):
            task = _resolve_task("CondadosAI/rtdetr_r50vd", revision="x")
        assert task == "detection"
        assert "CondadosAI/rtdetr_r50vd" in TASK_FALLBACK  # sanity

    def test_non_string_acaua_task_ignored(self, tmp_path: Path) -> None:
        """If `acaua_task` is present but not a string (malformed), fall back."""
        cfg_path = _write_config(tmp_path, {"acaua_task": 42})
        with patch("huggingface_hub.hf_hub_download", return_value=cfg_path):
            task = _resolve_task("CondadosAI/rtdetr_r50vd", revision="x")
        assert task == "detection"  # from fallback dict


class TestResolveTaskFailures:
    def test_missing_field_missing_fallback_raises(self, tmp_path: Path) -> None:
        cfg_path = _write_config(tmp_path, {"model_type": "rtdetr"})
        with (
            patch("huggingface_hub.hf_hub_download", return_value=cfg_path),
            pytest.raises(TaskResolutionError, match="Cannot determine task"),
        ):
            _resolve_task("CondadosAI/unknown-but-reachable", revision="x")

    def test_http_error_wraps_as_weight_download_error(self) -> None:
        hf_err = HfHubHTTPError("404 Not Found", response=None)
        with (
            patch("huggingface_hub.hf_hub_download", side_effect=hf_err),
            pytest.raises(WeightDownloadError, match=r"failed to fetch config\.json"),
        ):
            _resolve_task("CondadosAI/rtdetr_r50vd", revision="bogus")

    def test_arbitrary_download_error_wraps(self) -> None:
        with (
            patch("huggingface_hub.hf_hub_download", side_effect=RuntimeError("offline")),
            pytest.raises(WeightDownloadError, match="unexpected error"),
        ):
            _resolve_task("CondadosAI/rtdetr_r50vd", revision="x")

    def test_corrupt_config_json_raises(self, tmp_path: Path) -> None:
        bad = tmp_path / "config.json"
        bad.write_text("{not-json,,,")
        with (
            patch("huggingface_hub.hf_hub_download", return_value=str(bad)),
            pytest.raises(WeightDownloadError, match=r"failed to parse config\.json"),
        ):
            _resolve_task("CondadosAI/rtdetr_r50vd", revision="x")

    def test_missing_config_file_raises(self, tmp_path: Path) -> None:
        nonexistent = str(tmp_path / "does-not-exist.json")
        with (
            patch("huggingface_hub.hf_hub_download", return_value=nonexistent),
            pytest.raises(WeightDownloadError, match=r"failed to parse config\.json"),
        ):
            _resolve_task("CondadosAI/rtdetr_r50vd", revision="x")


class TestLoadAdapter:
    def test_detection_task_imports_rtdetr(self) -> None:
        cls = _load_adapter("detection")
        assert cls.__name__ == "RTDETRAdapter"

    def test_segmentation_task_imports_mask2former(self) -> None:
        cls = _load_adapter("segmentation")
        assert cls.__name__ == "Mask2FormerAdapter"

    def test_unknown_task_raises_with_supported_list(self) -> None:
        with pytest.raises(TaskResolutionError, match="Supported tasks"):
            _load_adapter("obb")

    def test_non_model_subclass_raises_type_error(self, monkeypatch: pytest.MonkeyPatch) -> None:
        from acaua._data import adapters as adapters_module

        monkeypatch.setitem(
            adapters_module.ADAPTERS, "bogus", ("acaua._data.licenses", "LicenseEntry")
        )
        with pytest.raises(TypeError, match="not a subclass"):
            _load_adapter("bogus")


class TestManifestConsistency:
    """Every adapter-targeted repo in the manifest must be routable.

    The fixture repo is the one intentional exception — it has `acaua_task`
    in its own config.json, so it doesn't need an entry in TASK_FALLBACK.
    """

    def test_fixture_not_in_fallback(self) -> None:
        assert "CondadosAI/acaua-task-test" in LICENSES
        assert "CondadosAI/acaua-task-test" not in TASK_FALLBACK

    def test_real_mirrors_have_fallback_entries(self) -> None:
        for repo_id, entry in LICENSES.items():
            if entry.upstream_src == "(synthetic test fixture)":
                continue
            assert repo_id in TASK_FALLBACK, f"{repo_id} missing from TASK_FALLBACK"
