# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""Tests for routing preset persistence (save/load/rename/delete/auto-load)."""

import json
from unittest import mock

import pytest

from familiar.core.provider_manager import (
    ProviderManager,
    _default_presets,
    _load_presets,
    _save_presets,
)


@pytest.fixture(autouse=True)
def _isolate_presets(tmp_path, monkeypatch):
    """Redirect presets file to a temp directory for every test."""
    import familiar.core.provider_manager as pm

    monkeypatch.setattr(pm, "_PRESETS_PATH", tmp_path / "routing_presets.json")


@pytest.fixture()
def manager():
    """Create a ProviderManager with a mock agent reference."""
    agent = mock.MagicMock()
    mgr = ProviderManager(agent)
    mgr._routing_mode = "manual"
    mgr._manual_routes = {
        "coding": "ollama/llama3.2",
        "complex": "ollama/llama3.2",
        "creative": "ollama/qwen2.5:7b",
        "long": "ollama/llama3.2",
        "moderate": "ollama/qwen2.5:7b",
        "private": "ollama/llama3.2",
        "simple": "ollama/qwen2.5:0.5b",
    }
    return mgr


class TestDefaultPresets:
    """Tests for default preset structure."""

    def test_default_presets_structure(self):
        data = _default_presets()
        assert data["version"] == 1
        assert data["active_preset"] is None
        assert len(data["presets"]) == 5
        for i, p in enumerate(data["presets"], 1):
            assert p["slot"] == i
            assert p["name"] == f"Preset {i}"
            assert p["mode"] is None
            assert p["routes"] is None

    def test_load_presets_returns_defaults_when_no_file(self):
        data = _load_presets()
        assert data == _default_presets()


class TestSavePreset:
    """Tests for saving presets."""

    def test_save_preset(self, manager):
        result = manager.save_preset(1, "Local Only")
        preset = next(p for p in result["presets"] if p["slot"] == 1)
        assert preset["name"] == "Local Only"
        assert preset["mode"] == "manual"
        assert preset["routes"] == manager._manual_routes
        # Verify persisted to disk
        on_disk = _load_presets()
        assert on_disk["presets"][0]["mode"] == "manual"

    def test_save_preset_without_name(self, manager):
        result = manager.save_preset(2)
        preset = next(p for p in result["presets"] if p["slot"] == 2)
        assert preset["name"] == "Preset 2"  # default name kept
        assert preset["mode"] == "manual"

    def test_save_preset_invalid_slot(self, manager):
        with pytest.raises(ValueError, match="Slot must be 1-5"):
            manager.save_preset(0)
        with pytest.raises(ValueError, match="Slot must be 1-5"):
            manager.save_preset(6)


class TestLoadPreset:
    """Tests for loading presets."""

    def test_load_preset(self, manager):
        manager.save_preset(1, "Test")
        # Change current config
        manager._routing_mode = "auto"
        manager._manual_routes = {}
        # Load saved preset
        result = manager.load_preset(1)
        assert manager._routing_mode == "manual"
        assert manager._manual_routes["coding"] == "ollama/llama3.2"
        assert result["mode"] == "manual"

    def test_load_empty_slot_raises(self, manager):
        with pytest.raises(ValueError, match="Slot 3 is empty"):
            manager.load_preset(3)


class TestActivePreset:
    """Tests for setting/clearing the active (default) preset."""

    def test_set_active_preset(self, manager):
        manager.save_preset(2, "Cloud")
        result = manager.set_active_preset(2)
        assert result["active_preset"] == 2

    def test_set_active_empty_raises(self, manager):
        with pytest.raises(ValueError, match="empty"):
            manager.set_active_preset(4)

    def test_clear_active_preset(self, manager):
        manager.save_preset(1, "Test")
        manager.set_active_preset(1)
        result = manager.set_active_preset(None)
        assert result["active_preset"] is None


class TestRenamePreset:
    """Tests for renaming presets."""

    def test_rename_preset(self, manager):
        result = manager.rename_preset(3, "My Custom")
        preset = next(p for p in result["presets"] if p["slot"] == 3)
        assert preset["name"] == "My Custom"


class TestDeletePreset:
    """Tests for deleting presets."""

    def test_delete_preset(self, manager):
        manager.save_preset(1, "To Delete")
        result = manager.delete_preset(1)
        preset = next(p for p in result["presets"] if p["slot"] == 1)
        assert preset["mode"] is None
        assert preset["routes"] is None
        assert preset["name"] == "Preset 1"

    def test_delete_active_clears_default(self, manager):
        manager.save_preset(2, "Active")
        manager.set_active_preset(2)
        result = manager.delete_preset(2)
        assert result["active_preset"] is None


class TestAutoLoad:
    """Tests for auto-loading preset on init_model_router."""

    def test_auto_load_on_init(self, manager):
        # Save a preset with custom routes
        custom_routes = dict(manager._manual_routes)
        custom_routes["simple"] = "ollama/tinyllama"
        manager._manual_routes = custom_routes
        manager.save_preset(1, "Custom")
        manager.set_active_preset(1)

        # Reset to defaults
        manager._routing_mode = "auto"
        manager._manual_routes = {}

        # Simulate the auto-load portion of init_model_router
        data = _load_presets()
        active = data.get("active_preset")
        assert active == 1
        preset = next(p for p in data["presets"] if p["slot"] == active)
        assert preset["mode"] == "manual"
        manager._routing_mode = preset["mode"]
        manager._manual_routes = dict(preset["routes"])

        assert manager._routing_mode == "manual"
        assert manager._manual_routes["simple"] == "ollama/tinyllama"


class TestAtomicWrite:
    """Tests for atomic file writing."""

    def test_atomic_write(self, tmp_path, monkeypatch):
        import familiar.core.provider_manager as pm

        path = tmp_path / "routing_presets.json"
        monkeypatch.setattr(pm, "_PRESETS_PATH", path)

        data = _default_presets()
        _save_presets(data)
        assert path.exists()
        # No leftover .tmp file
        assert not path.with_suffix(".tmp").exists()
        # Valid JSON
        loaded = json.loads(path.read_text())
        assert loaded["version"] == 1
