import types

import pytest

from huesoporro.bot import SaveMessagesCog


class DummyDB:
    def __init__(self, *args, **kwargs):
        pass


class FakeChannel:
    def __init__(self):
        self.sent: list[str] = []

    async def send(self, content: str):
        self.sent.append(content)


class FakeAuthor:
    def __init__(self, name: str):
        self.name = name


class FakeMessage:
    def __init__(self, content: str | None, author_name: str = "alice"):
        self.content = content
        self.author = FakeAuthor(author_name)
        self.channel = FakeChannel()


class FakeBot:
    def __init__(self, nick: str = "Junie", channel: str = "testchan"):
        self.nick = nick
        self.channel = channel


@pytest.fixture(autouse=True)
def patch_markov_and_svcs(monkeypatch):
    monkeypatch.setattr("huesoporro.bot.MarkovDatabase", DummyDB)

    class _DummyStoreSvc:
        def __init__(self, db):
            self.db = db

        async def run(self, content: str | None):
            return None

    class _DummyGenSvc:
        def __init__(self, db):
            self.db = db

        async def run(self, seed: str):
            return None

    monkeypatch.setattr("huesoporro.bot.SentenceStorerSvc", _DummyStoreSvc)
    monkeypatch.setattr("huesoporro.bot.SentenceGeneratorSvc", _DummyGenSvc)


@pytest.fixture
def cog(monkeypatch) -> SaveMessagesCog:
    bot = FakeBot()
    return SaveMessagesCog(bot)  # type: ignore[arg-type]


def make_async_fn(result=None, exc: Exception | None = None):
    async def _fn(*args, **kwargs):
        if exc:
            raise exc
        return result

    return _fn


@pytest.mark.asyncio
async def test_handle_bot_mention_returns_none_on_empty_content(cog: SaveMessagesCog):
    msg = FakeMessage(content=None)
    res = await cog._handle_bot_mention(msg)  # type: ignore[attr-defined]
    assert res is None
    assert msg.channel.sent == []


@pytest.mark.asyncio
async def test_handle_bot_mention_returns_none_when_no_mention(cog: SaveMessagesCog):
    msg = FakeMessage(content="hello world")
    res = await cog._handle_bot_mention(msg)  # type: ignore[attr-defined]
    assert res is None
    assert msg.channel.sent == []


@pytest.mark.asyncio
async def test_handle_bot_mention_returns_none_when_only_mention(
    cog: SaveMessagesCog, monkeypatch
):
    msg = FakeMessage(content=cog.bot.nick)  # type: ignore[attr-defined]
    res = await cog._handle_bot_mention(msg)  # type: ignore[attr-defined]
    assert res is None
    assert msg.channel.sent == []


@pytest.mark.asyncio
async def test_handle_bot_mention_generates_and_sends_reply(
    cog: SaveMessagesCog, monkeypatch
):
    msg = FakeMessage(content="juNie hello there")

    monkeypatch.setattr(
        cog, "generate_svc", types.SimpleNamespace(run=make_async_fn("foo bar"))
    )

    res = await cog._handle_bot_mention(msg)  # type: ignore[attr-defined]

    assert res == "foo bar"
    assert len(msg.channel.sent) == 1
    assert msg.channel.sent[0].startswith(f"@{msg.author.name} ")
    assert msg.channel.sent[0].endswith("foo bar")


@pytest.mark.asyncio
async def test_handle_bot_mention_no_send_when_generator_returns_none(
    cog: SaveMessagesCog, monkeypatch
):
    msg = FakeMessage(content=f"{cog.bot.nick} hello")  # type: ignore[attr-defined]

    monkeypatch.setattr(
        cog, "generate_svc", types.SimpleNamespace(run=make_async_fn(None))
    )

    res = await cog._handle_bot_mention(msg)  # type: ignore[attr-defined]
    assert res is None
    assert msg.channel.sent == []


@pytest.mark.asyncio
async def test_handle_bot_mention_swallows_exceptions_and_returns_none(
    cog: SaveMessagesCog, monkeypatch
):
    msg = FakeMessage(content=f"{cog.bot.nick} hello")  # type: ignore[attr-defined]

    monkeypatch.setattr(
        cog,
        "generate_svc",
        types.SimpleNamespace(run=make_async_fn(exc=RuntimeError("boom"))),
    )

    res = await cog._handle_bot_mention(msg)  # type: ignore[attr-defined]
    assert res is None
    assert msg.channel.sent == []
