"""
Tests for brynq.welcome — Agent Welcome System.

Covers:
- Welcome sequence generation for various model combos
- Help response generation
- First chain suggestion
- Message variation (not always the same)
- Edge cases (empty models, unknown models)
"""

import pytest
from collections import Counter

from brynq.welcome import (
    generate_welcome_sequence,
    generate_help_response,
    generate_first_chain_suggestion,
    get_model_display_info,
    _get_profile,
    _is_local,
    _is_cloud,
)


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _senders(msgs: list[dict]) -> list[str]:
    """Extract sender names from a message sequence."""
    return [m["sender"] for m in msgs]


def _content_concat(msgs: list[dict]) -> str:
    """Concatenate all message content for keyword searching."""
    return " ".join(m["content"] for m in msgs)


# ---------------------------------------------------------------------------
# Model Profile Resolution
# ---------------------------------------------------------------------------


class TestModelProfiles:
    def test_known_model_exact(self):
        p = _get_profile("llama3")
        assert p["display_name"] == "Llama 3"

    def test_known_model_prefix(self):
        """'mistral' should match even if the user passes 'mistral-7b'."""
        p = _get_profile("mistral-7b")
        assert p["display_name"] == "Mistral"

    def test_unknown_model_gets_fallback(self):
        p = _get_profile("some_random_model")
        # Fallback profile has {model_id} placeholder
        assert "intros" in p
        assert len(p["intros"]) > 0

    def test_is_local_for_ollama_models(self):
        assert _is_local("llama3") is True
        assert _is_local("mistral") is True
        assert _is_local("phi") is True

    def test_is_cloud_for_api_models(self):
        assert _is_cloud("claude") is True
        assert _is_cloud("gemini") is True
        assert _is_cloud("gpt") is True

    def test_local_not_cloud(self):
        assert _is_cloud("llama3") is False
        assert _is_local("claude") is False


# ---------------------------------------------------------------------------
# Welcome Sequence: Structure
# ---------------------------------------------------------------------------


class TestWelcomeSequenceStructure:
    def test_empty_models_returns_empty(self):
        assert generate_welcome_sequence([]) == []

    def test_single_model_returns_messages(self):
        msgs = generate_welcome_sequence(["llama3"])
        assert len(msgs) >= 2  # at least intro + capability or invite
        for m in msgs:
            assert "sender" in m
            assert "model" in m
            assert "content" in m
            assert "delay_ms" in m

    def test_two_local_models(self):
        msgs = generate_welcome_sequence(["llama3", "mistral"])
        assert len(msgs) >= 3
        senders = _senders(msgs)
        assert "Llama 3" in senders
        assert "Mistral" in senders

    def test_message_keys_present(self):
        msgs = generate_welcome_sequence(["llama3"])
        for m in msgs:
            assert set(m.keys()) == {"sender", "model", "content", "delay_ms", "badge"}

    def test_badges_correct(self):
        msgs = generate_welcome_sequence(["llama3", "claude"])
        badges = {m["model"]: m["badge"] for m in msgs}
        # llama3 should appear with Local badge at least once
        llama_badges = [m["badge"] for m in msgs if m["model"] == "llama3"]
        claude_badges = [m["badge"] for m in msgs if m["model"] == "claude"]
        assert all(b == "Local" for b in llama_badges)
        assert all(b == "Cloud" for b in claude_badges)


# ---------------------------------------------------------------------------
# Welcome Sequence: Timing
# ---------------------------------------------------------------------------


class TestWelcomeSequenceTiming:
    def test_first_message_at_500ms(self):
        msgs = generate_welcome_sequence(["llama3"])
        assert msgs[0]["delay_ms"] == 500

    def test_delays_are_increasing(self):
        msgs = generate_welcome_sequence(["llama3", "mistral"])
        delays = [m["delay_ms"] for m in msgs]
        for i in range(1, len(delays)):
            assert delays[i] > delays[i - 1], f"Delay {i} not increasing: {delays}"

    def test_delay_spacing_reasonable(self):
        """Gaps between messages should be 1000-3000ms (with some tolerance)."""
        msgs = generate_welcome_sequence(["llama3", "mistral"])
        delays = [m["delay_ms"] for m in msgs]
        for i in range(1, len(delays)):
            gap = delays[i] - delays[i - 1]
            assert 1000 <= gap <= 3500, f"Gap {gap}ms between messages {i-1} and {i}"


# ---------------------------------------------------------------------------
# Welcome Sequence: Content Scenarios
# ---------------------------------------------------------------------------


class TestWelcomeScenarios:
    def test_solo_local_mentions_adding_cloud(self):
        """A solo local model should tell the user how to add cloud models."""
        msgs = generate_welcome_sequence(["llama3"])
        text = _content_concat(msgs)
        assert "cloud" in text.lower() or "claude" in text.lower() or "gemini" in text.lower()

    def test_solo_cloud_mentions_local(self):
        """A solo cloud model should mention Ollama / local models."""
        msgs = generate_welcome_sequence(["claude"])
        text = _content_concat(msgs)
        assert "local" in text.lower() or "ollama" in text.lower()

    def test_two_locals_mention_cloud_invite(self):
        msgs = generate_welcome_sequence(["llama3", "mistral"])
        text = _content_concat(msgs)
        assert "claude" in text.lower() or "cloud" in text.lower() or "api" in text.lower()

    def test_mixed_team_collaborative(self):
        """Local + cloud should produce collaborative welcome."""
        msgs = generate_welcome_sequence(["llama3", "claude"])
        senders = _senders(msgs)
        assert "Llama 3" in senders
        assert "Claude" in senders
        # Should mention working together
        text = _content_concat(msgs)
        assert "team" in text.lower() or "together" in text.lower() or "chain" in text.lower()

    def test_three_model_team(self):
        """Three models should all get a voice."""
        msgs = generate_welcome_sequence(["llama3", "mistral", "claude"])
        senders = set(_senders(msgs))
        assert "Llama 3" in senders
        assert "Mistral" in senders
        assert "Claude" in senders

    def test_unknown_model_still_works(self):
        msgs = generate_welcome_sequence(["some_new_model"])
        assert len(msgs) >= 1
        assert msgs[0]["sender"] == "Some_New_Model"


# ---------------------------------------------------------------------------
# Message Variation
# ---------------------------------------------------------------------------


class TestMessageVariation:
    def test_intro_varies_across_calls(self):
        """Run the welcome sequence many times; intros should not always be identical."""
        intros = set()
        for _ in range(20):
            msgs = generate_welcome_sequence(["llama3"])
            intros.add(msgs[0]["content"])

        # With 4 intro variations, 20 runs should produce at least 2 unique ones
        assert len(intros) >= 2, f"Only got {len(intros)} unique intro(s) in 20 runs"

    def test_chain_suggestion_varies(self):
        """First chain suggestions should vary."""
        suggestions = set()
        for _ in range(20):
            s = generate_first_chain_suggestion(["llama3", "claude"])
            suggestions.add(s)
        assert len(suggestions) >= 2, f"Only got {len(suggestions)} unique suggestion(s)"


# ---------------------------------------------------------------------------
# Help Responses
# ---------------------------------------------------------------------------


class TestHelpResponses:
    def test_add_claude_guide(self):
        resp = generate_help_response("help me add Claude")
        assert "anthropic" in resp.lower() or "claude" in resp.lower()
        # Response may reference subscription login or API keys
        assert "login" in resp.lower() or "api" in resp.lower() or "key" in resp.lower()

    def test_add_gemini_guide(self):
        resp = generate_help_response("help me add Gemini")
        assert "google" in resp.lower() or "aistudio" in resp.lower()

    def test_add_gpt_guide(self):
        resp = generate_help_response("help me add GPT")
        assert "openai" in resp.lower()

    def test_add_openai_alias(self):
        """'openai' should also trigger the GPT guide."""
        resp = generate_help_response("add openai")
        assert "openai" in resp.lower() or "platform.openai.com" in resp

    def test_capabilities_with_models(self):
        resp = generate_help_response("what can you do", ["llama3", "mistral"])
        assert "Llama 3" in resp
        assert "Mistral" in resp

    def test_capabilities_no_models(self):
        resp = generate_help_response("what can you do", [])
        assert "ollama" in resp.lower() or "Ollama" in resp

    def test_chain_explanation(self):
        resp = generate_help_response("how do chains work", ["llama3", "claude"])
        assert "chain" in resp.lower()
        # Should mention the specific models available
        assert "Llama" in resp or "Claude" in resp

    def test_generic_help(self):
        resp = generate_help_response("help")
        assert "add Claude" in resp or "add claude" in resp.lower()
        assert "chain" in resp.lower()

    def test_connect_claude_variant(self):
        """'connect Claude' should also trigger the guide (not just 'add')."""
        resp = generate_help_response("connect Claude")
        assert "anthropic" in resp.lower() or "console.anthropic.com" in resp

    def test_setup_gemini_variant(self):
        resp = generate_help_response("setup Gemini")
        assert "google" in resp.lower() or "aistudio" in resp.lower()


# ---------------------------------------------------------------------------
# First Chain Suggestion
# ---------------------------------------------------------------------------


class TestFirstChainSuggestion:
    def test_single_model_suggests_adding_more(self):
        s = generate_first_chain_suggestion(["llama3"])
        assert "add" in s.lower() or "another" in s.lower()

    def test_local_plus_cloud_suggests_chain(self):
        s = generate_first_chain_suggestion(["llama3", "claude"])
        assert "Llama" in s or "Claude" in s
        # Should contain a concrete example
        assert '"' in s or "try" in s.lower()

    def test_two_cloud_models(self):
        s = generate_first_chain_suggestion(["claude", "gemini"])
        assert "Claude" in s
        assert "Gemini" in s

    def test_two_local_models(self):
        s = generate_first_chain_suggestion(["llama3", "mistral"])
        assert "Llama" in s or "Mistral" in s
        assert "local" in s.lower() or "free" in s.lower() or "hardware" in s.lower()

    def test_empty_models(self):
        s = generate_first_chain_suggestion([])
        assert "add" in s.lower()

    def test_many_models(self):
        """Should work with more than 2 models."""
        s = generate_first_chain_suggestion(["llama3", "mistral", "claude", "gemini"])
        assert len(s) > 20  # sanity: not an empty response


# ---------------------------------------------------------------------------
# Display Info
# ---------------------------------------------------------------------------


class TestModelDisplayInfo:
    def test_known_model(self):
        info = get_model_display_info("llama3")
        assert info["display_name"] == "Llama 3"
        assert info["badge"] == "Local"
        assert info["color"].startswith("#")
        assert len(info["strengths"]) > 0

    def test_cloud_model(self):
        info = get_model_display_info("claude")
        assert info["badge"] == "Cloud"
        assert info["display_name"] == "Claude"

    def test_unknown_model_gets_titled_name(self):
        info = get_model_display_info("custom_thing")
        assert info["display_name"] == "Custom_Thing"
        assert info["model_id"] == "custom_thing"
