"""
Unit tests for ArielAI — no GPU or model downloads required.
All backend calls are mocked.
"""

import pytest
from unittest.mock import MagicMock, patch

import arielai
from arielai import Chatbot, list_presets, get_preset, create_backend
from arielai.backends import BaseBackend


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------


class MockBackend(BaseBackend):
    """In-memory backend that echoes messages."""

    def generate(self, message, history, system_prompt="", max_new_tokens=512, temperature=0.7):
        return f"Echo: {message}"

    def generate_stream(self, message, history, system_prompt="", max_new_tokens=512, temperature=0.7):
        yield f"Echo: {message}"


@pytest.fixture
def mock_backend():
    return MockBackend()


@pytest.fixture
def bot(mock_backend):
    return Chatbot(backend_instance=mock_backend)


# ---------------------------------------------------------------------------
# Chatbot construction
# ---------------------------------------------------------------------------


class TestChatbotInit:
    def test_default_construction(self, mock_backend):
        bot = Chatbot(backend_instance=mock_backend)
        assert bot.model == "microsoft/DialoGPT-medium"
        assert bot.max_new_tokens == 512
        assert bot.temperature == 0.7
        assert bot.streaming is True

    def test_custom_construction(self, mock_backend):
        bot = Chatbot(
            model="my-model",
            title="My Bot",
            system_prompt="Be helpful.",
            max_new_tokens=256,
            temperature=0.5,
            backend_instance=mock_backend,
        )
        assert bot.model == "my-model"
        assert bot.title == "My Bot"
        assert bot.system_prompt == "Be helpful."
        assert bot.max_new_tokens == 256
        assert bot.temperature == 0.5


# ---------------------------------------------------------------------------
# from_preset
# ---------------------------------------------------------------------------


class TestFromPreset:
    def test_known_preset(self):
        bot = Chatbot.from_preset("tiny", backend_instance=MockBackend())
        assert bot.model == "facebook/blenderbot-400M-distill"
        assert bot.backend_name == "transformers"

    def test_unknown_preset_raises(self):
        with pytest.raises(ValueError, match="Unknown preset"):
            Chatbot.from_preset("nonexistent-preset")

    def test_preset_kwargs_override(self):
        bot = Chatbot.from_preset("tiny", title="Custom Title", backend_instance=MockBackend())
        assert bot.title == "Custom Title"


# ---------------------------------------------------------------------------
# Chat
# ---------------------------------------------------------------------------


class TestChat:
    def test_chat_non_streaming(self, mock_backend):
        bot = Chatbot(backend_instance=mock_backend, streaming=False)
        result = list(bot.chat("Hello", []))
        assert result == ["Echo: Hello"]

    def test_chat_streaming(self, mock_backend):
        bot = Chatbot(backend_instance=mock_backend, streaming=True)
        result = list(bot.chat("Hello", []))
        assert result == ["Echo: Hello"]

    def test_chat_is_always_generator(self, mock_backend):
        import types
        bot = Chatbot(backend_instance=mock_backend, streaming=False)
        assert isinstance(bot.chat("Hi", []), types.GeneratorType)
        bot2 = Chatbot(backend_instance=mock_backend, streaming=True)
        assert isinstance(bot2.chat("Hi", []), types.GeneratorType)

    def test_chat_passes_history(self):
        history_received = []

        class HistoryCapture(BaseBackend):
            def generate(self, message, history, **kwargs):
                history_received.extend(history)
                return "ok"

        bot = Chatbot(backend_instance=HistoryCapture(), streaming=False)
        history = [("hi", "hello"), ("bye", "goodbye")]
        list(bot.chat("new message", history))
        assert history_received == history


# ---------------------------------------------------------------------------
# Models / presets
# ---------------------------------------------------------------------------


class TestPresets:
    def test_list_presets_returns_dict(self):
        presets = list_presets()
        assert isinstance(presets, dict)
        assert len(presets) > 0

    def test_all_presets_have_descriptions(self):
        for name, desc in list_presets().items():
            assert isinstance(desc, str), f"Preset '{name}' missing description"
            assert len(desc) > 0

    def test_get_preset_known(self):
        preset = get_preset("tiny")
        assert preset is not None
        assert preset.model_id == "facebook/blenderbot-400M-distill"

    def test_get_preset_unknown_returns_none(self):
        assert get_preset("does-not-exist") is None


# ---------------------------------------------------------------------------
# Backends
# ---------------------------------------------------------------------------


class TestCreateBackend:
    def test_transformers_backend(self):
        backend = create_backend("transformers", "some/model")
        from arielai.backends import TransformersBackend
        assert isinstance(backend, TransformersBackend)

    def test_ollama_backend(self):
        backend = create_backend("ollama", "llama3")
        from arielai.backends import OllamaBackend
        assert isinstance(backend, OllamaBackend)

    def test_inference_backend(self):
        backend = create_backend("inference", "some/model")
        from arielai.backends import HFInferenceBackend
        assert isinstance(backend, HFInferenceBackend)

    def test_unknown_backend_raises(self):
        with pytest.raises(ValueError, match="Unknown backend"):
            create_backend("unknown-backend", "model")


# ---------------------------------------------------------------------------
# Package-level launch function
# ---------------------------------------------------------------------------


class TestLaunchHelper:
    def test_launch_accepts_kwargs(self):
        with patch("arielai.Chatbot.launch") as mock_launch, \
             patch("arielai.Chatbot.__init__", return_value=None) as mock_init:
            mock_init.return_value = None
            try:
                arielai.launch("my/model", title="Test", share=True)
            except Exception:
                pass
