"""Unit tests for ArielAI v0.2.2 — no GPU, no network, no API keys required."""

import types
import pytest
from unittest.mock import patch

import arielai
from arielai import Chatbot, list_presets, get_preset, create_backend
from arielai.backends import (
    BaseBackend, OpenAIBackend, TransformersBackend,
    OllamaBackend, HFInferenceBackend,
    _normalize_history,
)


# ---------------------------------------------------------------------------
# Mock backend
# ---------------------------------------------------------------------------

class MockBackend(BaseBackend):
    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)


# ---------------------------------------------------------------------------
# History normalisation
# ---------------------------------------------------------------------------

class TestNormalizeHistory:
    def test_empty(self):
        assert _normalize_history([]) == []

    def test_tuple_format(self):
        h = [("hi", "hello"), ("bye", "ok")]
        assert _normalize_history(h) == [("hi", "hello"), ("bye", "ok")]

    def test_dict_format(self):
        h = [
            {"role": "user", "content": "hi"},
            {"role": "assistant", "content": "hello"},
        ]
        assert _normalize_history(h) == [("hi", "hello")]

    def test_list_format(self):
        assert _normalize_history([["a", "b"], ["c", "d"]]) == [("a", "b"), ("c", "d")]


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

class TestChatbotInit:
    def test_default_construction(self, mock_backend):
        bot = Chatbot(backend_instance=mock_backend)
        assert bot.max_new_tokens == 512
        assert bot.temperature == 0.7
        assert bot.streaming is True

    def test_custom_values(self, mock_backend):
        bot = Chatbot(model="my-model", title="Test", max_new_tokens=128,
                      temperature=0.3, backend_instance=mock_backend)
        assert bot.model == "my-model"
        assert bot.title == "Test"
        assert bot.max_new_tokens == 128
        assert bot.temperature == 0.3

    def test_repr(self, mock_backend):
        bot = Chatbot(model="test-model", backend_instance=mock_backend)
        assert "test-model" in repr(bot)

    def test_openai_backend_selected_when_key_given(self):
        bot = Chatbot(openai_api_key="sk-fake")
        assert bot.backend_name == "openai"
        assert isinstance(bot._backend, OpenAIBackend)

    def test_openai_explicit_model(self):
        bot = Chatbot("gpt-4o", openai_api_key="sk-fake")
        assert bot.model == "gpt-4o"
        assert isinstance(bot._backend, OpenAIBackend)


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

class TestFromPreset:
    def test_known_preset(self):
        bot = Chatbot.from_preset("qwen", backend_instance=MockBackend())
        assert "Qwen" in bot.model

    def test_gpt4o_mini_preset_exists(self):
        p = get_preset("gpt4o-mini")
        assert p is not None
        assert p.backend == "openai"

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

    def test_kwargs_override(self):
        bot = Chatbot.from_preset("qwen", title="Custom", backend_instance=MockBackend())
        assert bot.title == "Custom"


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

class TestChat:
    def test_always_returns_generator(self, mock_backend):
        bot = Chatbot(backend_instance=mock_backend, streaming=False)
        assert isinstance(bot.chat("Hi", []), types.GeneratorType)

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

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

    def test_normalises_dict_history(self):
        received = []
        class Capture(BaseBackend):
            def generate(self, message, history, **kwargs):
                received.extend(history); return "ok"

        bot = Chatbot(backend_instance=Capture(), streaming=False)
        dict_h = [{"role": "user", "content": "hi"},
                  {"role": "assistant", "content": "hello"}]
        list(bot.chat("new", dict_h))
        assert received == [("hi", "hello")]


# ---------------------------------------------------------------------------
# Presets
# ---------------------------------------------------------------------------

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

    def test_openai_presets_present(self):
        p = list_presets()
        assert "gpt4o-mini" in p
        assert "gpt4o" in p

    def test_local_presets_present(self):
        p = list_presets()
        assert "qwen" in p
        assert "tinyllama" in p
        assert "llama3" in p

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


# ---------------------------------------------------------------------------
# Backends / Factory
# ---------------------------------------------------------------------------

class TestCreateBackend:
    def test_transformers(self):
        assert isinstance(create_backend("transformers", "m"), TransformersBackend)

    def test_ollama(self):
        assert isinstance(create_backend("ollama", "m"), OllamaBackend)

    def test_openai(self):
        assert isinstance(create_backend("openai", "m"), OpenAIBackend)

    def test_inference(self):
        assert isinstance(create_backend("inference", "m"), HFInferenceBackend)

    def test_unknown_raises(self):
        with pytest.raises(ValueError):
            create_backend("bad", "m")


# ---------------------------------------------------------------------------
# Auto-backend
# ---------------------------------------------------------------------------

class TestAutoSelect:
    def test_no_ollama_falls_back_to_transformers(self):
        from arielai.backends import auto_select_backend
        with patch("arielai.backends._ollama_running", return_value=False):
            backend, model = auto_select_backend()
            assert backend == "transformers"
            assert model

    def test_ollama_with_models(self):
        from arielai.backends import auto_select_backend
        with patch("arielai.backends._ollama_running", return_value=True), \
             patch("arielai.backends._list_ollama_models", return_value=["llama3.2:3b"]):
            backend, model = auto_select_backend()
            assert backend == "ollama"
            assert "llama3" in model


# ---------------------------------------------------------------------------
# UI
# ---------------------------------------------------------------------------

class TestUIImport:
    def test_chatui_importable(self):
        from arielai import ChatUI
        assert ChatUI is not None

    def test_accent_colors_all_present(self):
        from arielai import ACCENT_COLORS
        for key in ("indigo", "violet", "rose", "emerald", "amber", "blue", "cyan", "slate"):
            assert key in ACCENT_COLORS
