"""Tests for the chat_bridge base classes, registry, and invite parser."""

import pytest

from integrations.chat_bridge.base import (
    ChatMessage,
    ChatPlatform,
    ChatThread,
    InviteInfo,
    PlatformState,
    PlatformStatus,
)
from integrations.chat_bridge.registry import PlatformRegistry

# ── Base dataclass tests ───────────────────────────────────────────────────────


class TestChatMessage:
    def test_create_message(self):
        msg = ChatMessage(
            content="Hello",
            author="user1",
            channel_id="123",
            platform="test",
        )
        assert msg.content == "Hello"
        assert msg.author == "user1"
        assert msg.platform == "test"
        assert msg.thread_id is None
        assert msg.attachments == []

    def test_message_with_thread(self):
        msg = ChatMessage(
            content="Reply",
            author="bot",
            channel_id="123",
            platform="discord",
            thread_id="456",
        )
        assert msg.thread_id == "456"


class TestChatThread:
    def test_create_thread(self):
        thread = ChatThread(
            thread_id="t1",
            title="Timmy | user1",
            channel_id="c1",
            platform="discord",
        )
        assert thread.thread_id == "t1"
        assert thread.archived is False
        assert thread.message_count == 0


class TestInviteInfo:
    def test_create_invite(self):
        invite = InviteInfo(
            url="https://discord.gg/abc123",
            code="abc123",
            platform="discord",
            source="qr",
        )
        assert invite.code == "abc123"
        assert invite.source == "qr"


class TestPlatformStatus:
    def test_to_dict(self):
        status = PlatformStatus(
            platform="discord",
            state=PlatformState.CONNECTED,
            token_set=True,
            guild_count=3,
        )
        d = status.to_dict()
        assert d["connected"] is True
        assert d["platform"] == "discord"
        assert d["guild_count"] == 3
        assert d["state"] == "connected"

    def test_disconnected_status(self):
        status = PlatformStatus(
            platform="test",
            state=PlatformState.DISCONNECTED,
            token_set=False,
        )
        d = status.to_dict()
        assert d["connected"] is False


# ── PlatformRegistry tests ────────────────────────────────────────────────────


class _FakePlatform(ChatPlatform):
    """Minimal ChatPlatform for testing the registry."""

    def __init__(self, platform_name: str = "fake"):
        self._name = platform_name
        self._state = PlatformState.DISCONNECTED

    @property
    def name(self) -> str:
        return self._name

    @property
    def state(self) -> PlatformState:
        return self._state

    async def start(self, token=None) -> bool:
        self._state = PlatformState.CONNECTED
        return True

    async def stop(self) -> None:
        self._state = PlatformState.DISCONNECTED

    async def send_message(self, channel_id, content, thread_id=None):
        return ChatMessage(
            content=content, author="bot", channel_id=channel_id, platform=self._name
        )

    async def create_thread(self, channel_id, title, initial_message=None):
        return ChatThread(thread_id="t1", title=title, channel_id=channel_id, platform=self._name)

    async def join_from_invite(self, invite_code) -> bool:
        return True

    def status(self):
        return PlatformStatus(
            platform=self._name,
            state=self._state,
            token_set=False,
        )

    def save_token(self, token):
        pass

    def load_token(self):
        return None


class TestPlatformRegistry:
    def test_register_and_get(self):
        reg = PlatformRegistry()
        p = _FakePlatform("test1")
        reg.register(p)
        assert reg.get("test1") is p

    def test_get_missing(self):
        reg = PlatformRegistry()
        assert reg.get("nonexistent") is None

    def test_unregister(self):
        reg = PlatformRegistry()
        p = _FakePlatform("test1")
        reg.register(p)
        assert reg.unregister("test1") is True
        assert reg.get("test1") is None

    def test_unregister_missing(self):
        reg = PlatformRegistry()
        assert reg.unregister("nope") is False

    def test_list_platforms(self):
        reg = PlatformRegistry()
        reg.register(_FakePlatform("a"))
        reg.register(_FakePlatform("b"))
        statuses = reg.list_platforms()
        assert len(statuses) == 2
        names = {s.platform for s in statuses}
        assert names == {"a", "b"}

    @pytest.mark.asyncio
    async def test_start_all(self):
        reg = PlatformRegistry()
        reg.register(_FakePlatform("x"))
        reg.register(_FakePlatform("y"))
        results = await reg.start_all()
        assert results == {"x": True, "y": True}

    @pytest.mark.asyncio
    async def test_stop_all(self):
        reg = PlatformRegistry()
        p = _FakePlatform("z")
        reg.register(p)
        await reg.start_all()
        assert p.state == PlatformState.CONNECTED
        await reg.stop_all()
        assert p.state == PlatformState.DISCONNECTED

    def test_replace_existing(self):
        reg = PlatformRegistry()
        p1 = _FakePlatform("dup")
        p2 = _FakePlatform("dup")
        reg.register(p1)
        reg.register(p2)
        assert reg.get("dup") is p2


# ── InviteParser tests ────────────────────────────────────────────────────────


class TestInviteParser:
    def test_parse_text_discord_gg(self):
        from integrations.chat_bridge.invite_parser import invite_parser

        result = invite_parser.parse_text("Join us at https://discord.gg/abc123!")
        assert result is not None
        assert result.code == "abc123"
        assert result.platform == "discord"
        assert result.source == "text"

    def test_parse_text_discord_com_invite(self):
        from integrations.chat_bridge.invite_parser import invite_parser

        result = invite_parser.parse_text("Link: https://discord.com/invite/myServer2024")
        assert result is not None
        assert result.code == "myServer2024"

    def test_parse_text_discordapp(self):
        from integrations.chat_bridge.invite_parser import invite_parser

        result = invite_parser.parse_text("https://discordapp.com/invite/test-code")
        assert result is not None
        assert result.code == "test-code"

    def test_parse_text_no_invite(self):
        from integrations.chat_bridge.invite_parser import invite_parser

        result = invite_parser.parse_text("Hello world, no links here")
        assert result is None

    def test_parse_text_bare_discord_gg(self):
        from integrations.chat_bridge.invite_parser import invite_parser

        result = invite_parser.parse_text("discord.gg/xyz789")
        assert result is not None
        assert result.code == "xyz789"

    @pytest.mark.asyncio
    async def test_parse_image_no_deps(self):
        """parse_image returns None when pyzbar/Pillow are not installed."""
        from unittest.mock import AsyncMock, patch

        from integrations.chat_bridge.invite_parser import InviteParser

        parser = InviteParser()
        # Mock out the Ollama vision call so we don't make a real HTTP request
        with patch.object(parser, "_try_ollama_vision", new_callable=AsyncMock, return_value=None):
            # With mocked pyzbar + mocked vision, this should gracefully return None
            result = await parser.parse_image(b"fake-image-bytes")
        assert result is None


class TestExtractDiscordCode:
    def test_various_formats(self):
        from integrations.chat_bridge.invite_parser import _extract_discord_code

        assert _extract_discord_code("discord.gg/abc") == "abc"
        assert _extract_discord_code("https://discord.gg/test") == "test"
        assert _extract_discord_code("http://discord.gg/http") == "http"
        assert _extract_discord_code("discord.com/invite/xyz") == "xyz"
        assert _extract_discord_code("no link here") is None
        assert _extract_discord_code("") is None
