import asyncio
import time
import uuid

import pytest

from huesoporro.models import Chatbot, User
from huesoporro.svc.is_mod import IsModSvc


async def test_create_user_svc_returns_user(create_user_svc, user):
    created_user = await create_user_svc.run(user=user)
    assert created_user == user


async def test_update_user_svc_returns_user(update_user_svc, persisted_user):
    persisted_user.username = "new_username"
    updated_user = await update_user_svc.run(user=persisted_user)
    assert updated_user == persisted_user


async def test_update_user_svc_raises_value_error_for_non_existing_user(
    update_user_svc, user
):
    with pytest.raises(ValueError, match=f"User {user.username} does not exist"):
        await update_user_svc.run(user=user)


async def test_delete_user_svc_returns_none(delete_user_svc, persisted_user):
    assert await delete_user_svc.run(user=persisted_user) is None


async def test_get_user_by_id_svc_returns_user(get_user_by_id_svc, persisted_user):
    assert await get_user_by_id_svc.run(user_id=persisted_user.id) == persisted_user


async def test_get_user_by_id_returns_none(get_user_by_id_svc):
    assert await get_user_by_id_svc.run(user_id=uuid.uuid4()) is None


async def test_get_user_by_username(get_user_by_username_svc, persisted_user):
    assert (
        await get_user_by_username_svc.run(username=persisted_user.username)
        == persisted_user
    )


async def test_get_user_by_username_returns_none(get_user_by_username_svc):
    assert await get_user_by_username_svc.run(username="unknown_user") is None


async def test_is_mod_svc_returns_true_for_channel(is_mod_svc: IsModSvc, user: User):
    is_mod = await is_mod_svc.run(user=user, username="TestUser", channel="TestUser")
    assert is_mod


async def test_is_mod_svc_returns_true_for_user_in_modlist(
    is_mod_svc: IsModSvc,
    persisted_user: User,
    persisted_chatbot: Chatbot,
):
    assert persisted_chatbot.mods
    for mod in persisted_chatbot.mods:
        is_mod = await is_mod_svc.run(
            user=persisted_user, username=mod, channel=persisted_user.username
        )
        assert is_mod


async def test_is_mod_svc_returns_false_for_chatbotless_user(
    is_mod_svc: IsModSvc, user: User
):
    is_mod = await is_mod_svc.run(user=user, username="TestUser", channel="TestUser2")
    assert not is_mod


async def test_is_mod_svc_returns_false_for_user_not_in_modlist(
    is_mod_svc: IsModSvc, persisted_user: User, persisted_chatbot
):
    is_mod = await is_mod_svc.run(
        user=persisted_user, username="TestUser2", channel=persisted_user.username
    )
    assert not is_mod


async def test_backoff_svc_returns_for_first_attempt(
    backoff_svc, backoff_callable, async_backoff_callable
):
    assert backoff_svc.call(backoff_callable) == "foo"

    assert await backoff_svc.call_async(async_backoff_callable) == "async foo"


async def test_backoff_svc_returns_none_for_second_attempt(
    backoff_svc, backoff_callable, async_backoff_callable
):
    assert backoff_svc.call(backoff_callable) == "foo"
    assert backoff_svc.call(backoff_callable) is None

    assert await backoff_svc.call_async(async_backoff_callable) == "async foo"
    assert await backoff_svc.call_async(async_backoff_callable) is None


async def test_backoff_svc_returns_for_second_attempt_after_delay(
    backoff_svc, backoff_callable, async_backoff_callable
):
    assert backoff_svc.call(backoff_callable) == "foo"
    assert backoff_svc.call(backoff_callable) is None
    time.sleep(3)
    assert backoff_svc.call(backoff_callable) == "foo"

    assert await backoff_svc.call_async(async_backoff_callable) == "async foo"
    assert await backoff_svc.call_async(async_backoff_callable) is None
    await asyncio.sleep(3)
    assert await backoff_svc.call_async(async_backoff_callable) == "async foo"


async def test_backoff_svc_raises_value_error_for_unknown_callable(backoff_svc):
    with pytest.raises(ValueError, match="not registered with backoff service"):
        backoff_svc.call(lambda: "foo")


async def test_backoff_svc_raises_value_error_for_unknown_async_callable(backoff_svc):
    with pytest.raises(ValueError, match="not registered with backoff service"):
        await backoff_svc.call_async(lambda: "foo")


async def test_backoff_svc_raises_value_error_for_async_called_from_sync(
    backoff_svc, backoff_callable
):
    with pytest.raises(
        ValueError, match="Cannot call sync function with .call_async()"
    ):
        await backoff_svc.call_async(backoff_callable)


async def test_backoff_svc_raises_value_error_for_sync_called_from_async(
    backoff_svc, async_backoff_callable
):
    with pytest.raises(ValueError, match="Cannot call async function with .call()"):
        backoff_svc.call(async_backoff_callable)


async def test_create_quote_svc(create_quote_svc, quote):
    created_quote = await create_quote_svc.run(quote)
    assert created_quote == quote


async def test_get_random_quote_svc_returns_random_quote(
    get_random_quote_svc, persisted_quote
):
    random_quote = await get_random_quote_svc.run(
        channel_name=persisted_quote.channel_name
    )
    assert random_quote == persisted_quote


def test_generate_hello_svc_returns_greeting(hello_generator_svc, user):
    greeting = hello_generator_svc.run(username=user.username)
    assert greeting.split()[0] in hello_generator_svc.hellos


def test_generate_hello_svc_returns_none_on_already_greeted_user(
    hello_generator_svc, user
):
    assert hello_generator_svc.run(username=user.username)
    assert not hello_generator_svc.run(username=user.username)


async def test_create_chatbot_svc_returns_chatbot(create_chatbot_svc, chatbot):
    created_chatbot = await create_chatbot_svc.run(chatbot)
    assert created_chatbot == chatbot


async def test_get_chatbot_by_user_id_svc_returns_chatbot(
    get_chatbot_by_user_id_svc, persisted_chatbot
):
    existing_chatbot = await get_chatbot_by_user_id_svc.run(
        user_id=persisted_chatbot.user_id
    )
    assert existing_chatbot == persisted_chatbot


async def test_update_chatbot_svc_returns_chatbot(
    update_chatbot_svc, persisted_chatbot, faker
):
    persisted_chatbot.automatic_quote_timer = faker.pyint()
    persisted_chatbot.automatic_quote_timer = faker.pyint()
    persisted_chatbot.mods = [faker.word() for _ in range(5)]
    updated_chatbot = await update_chatbot_svc.run(chatbot=persisted_chatbot)
    persisted_chatbot.last_updated_at = updated_chatbot.last_updated_at
    assert updated_chatbot == persisted_chatbot
