"""
Tests for Phase 91: API Versioning for Backward Compatibility.
"""

import json

import pytest


@pytest.fixture(autouse=True)
def _isolate(tmp_path, monkeypatch):
    """Redirect api version storage to a temp directory."""
    api_dir = tmp_path / "api_versions"
    api_dir.mkdir(parents=True, exist_ok=True)

    import brynq.server as srv
    monkeypatch.setattr(srv, "BROKER_DIR", tmp_path)

    import brynq.api_versioning as mod
    monkeypatch.setattr(mod, "API_VER_DIR", api_dir)
    monkeypatch.setattr(mod, "VERSIONS_FILE", api_dir / "versions.json")
    monkeypatch.setattr(mod, "CHANGES_FILE", api_dir / "changes.jsonl")


# ── 1. Register Version ───────────────────────────────────────────────────


class TestRegisterVersion:
    def test_register_basic(self):
        from brynq.api_versioning import register_version, get_version
        result = register_version("v1", {"/tasks": "handle_tasks"})
        assert result["ok"] is True
        v = get_version("v1")
        assert v is not None
        assert v["version"] == "v1"
        assert "/tasks" in v["routes"]

    def test_register_deprecated(self):
        from brynq.api_versioning import register_version, get_version
        register_version("v0", {"/old": "old_handler"}, deprecated=True)
        v = get_version("v0")
        assert v["deprecated"] is True

    def test_register_multiple(self):
        from brynq.api_versioning import register_version, list_versions
        register_version("v1", {"/a": "ha"})
        register_version("v2", {"/b": "hb"})
        versions = list_versions()
        assert len(versions) == 2

    def test_register_overwrites(self):
        from brynq.api_versioning import register_version, get_version
        register_version("v1", {"/a": "old"})
        register_version("v1", {"/a": "new"})
        v = get_version("v1")
        assert v["routes"]["/a"] == "new"


# ── 2. List Versions ──────────────────────────────────────────────────────


class TestListVersions:
    def test_empty(self):
        from brynq.api_versioning import list_versions
        assert list_versions() == []

    def test_filter_active(self):
        from brynq.api_versioning import register_version, list_versions
        register_version("v1", {"/a": "h"})
        register_version("v2", {"/b": "h"}, deprecated=True)
        active = list_versions(status="active")
        assert len(active) == 1
        assert active[0]["version"] == "v1"

    def test_filter_deprecated(self):
        from brynq.api_versioning import register_version, list_versions
        register_version("v1", {"/a": "h"})
        register_version("v2", {"/b": "h"}, deprecated=True)
        dep = list_versions(status="deprecated")
        assert len(dep) == 1
        assert dep[0]["version"] == "v2"

    def test_sorted_order(self):
        from brynq.api_versioning import register_version, list_versions
        register_version("v2", {})
        register_version("v1", {})
        versions = list_versions()
        assert versions[0]["version"] == "v1"
        assert versions[1]["version"] == "v2"


# ── 3. Resolve Route ──────────────────────────────────────────────────────


class TestResolveRoute:
    def test_exact_match(self):
        from brynq.api_versioning import register_version, resolve_route
        register_version("v1", {"/tasks": "handle_tasks_v1"})
        result = resolve_route("v1", "/tasks")
        assert result is not None
        assert result["handler"] == "handle_tasks_v1"

    def test_not_found(self):
        from brynq.api_versioning import register_version, resolve_route
        register_version("v1", {"/tasks": "h"})
        result = resolve_route("v1", "/nonexistent")
        assert result is None

    def test_fallback_to_other_version(self):
        from brynq.api_versioning import register_version, resolve_route
        register_version("v1", {"/tasks": "h_v1"})
        register_version("v2", {"/users": "h_v2"})
        result = resolve_route("v2", "/tasks")
        assert result is not None
        assert result.get("fallback") is True
        assert result["handler"] == "h_v1"

    def test_deprecated_route(self):
        from brynq.api_versioning import register_version, resolve_route
        register_version("v1", {"/old": "h"}, deprecated=True)
        result = resolve_route("v1", "/old")
        assert result["deprecated"] is True


# ── 4. Deprecate Version ──────────────────────────────────────────────────


class TestDeprecateVersion:
    def test_deprecate(self):
        from brynq.api_versioning import register_version, deprecate_version, get_version
        register_version("v1", {"/a": "h"})
        result = deprecate_version("v1", sunset_date="2026-06-01")
        assert result["ok"] is True
        v = get_version("v1")
        assert v["deprecated"] is True
        assert v["sunset_date"] == "2026-06-01"

    def test_deprecate_nonexistent(self):
        from brynq.api_versioning import deprecate_version
        result = deprecate_version("v99")
        assert "error" in result

    def test_deprecate_without_sunset(self):
        from brynq.api_versioning import register_version, deprecate_version, get_version
        register_version("v1", {"/a": "h"})
        deprecate_version("v1")
        v = get_version("v1")
        assert v["deprecated"] is True
        assert v["sunset_date"] is None


# ── 5. Changes & Migration Guide ──────────────────────────────────────────


class TestChanges:
    def test_add_change(self):
        from brynq.api_versioning import add_change
        result = add_change("v1", "v2", "breaking", "Removed /old endpoint")
        assert result["ok"] is True

    def test_invalid_change_type(self):
        from brynq.api_versioning import add_change
        result = add_change("v1", "v2", "invalid_type", "desc")
        assert "error" in result

    def test_migration_guide(self):
        from brynq.api_versioning import add_change, get_migration_guide
        add_change("v1", "v2", "breaking", "Removed /old")
        add_change("v1", "v2", "addition", "Added /new")
        add_change("v2", "v3", "breaking", "Changed /tasks format")
        guide = get_migration_guide("v1", "v2")
        assert guide["total_changes"] == 2
        assert len(guide["breaking_changes"]) == 1
        assert len(guide["other_changes"]) == 1

    def test_migration_guide_empty(self):
        from brynq.api_versioning import get_migration_guide
        guide = get_migration_guide("v1", "v2")
        assert guide["total_changes"] == 0

    def test_all_change_types(self):
        from brynq.api_versioning import add_change
        for ct in ("breaking", "non-breaking", "addition", "removal"):
            result = add_change("v1", "v2", ct, f"test {ct}")
            assert result["ok"] is True
