from unittest.mock import MagicMock

import pytest

from huesoporro.actions.quotes.create_quote_action import CreateQuoteAction
from huesoporro.actions.quotes.get_random_quote import GetRandomQuoteAction
from huesoporro.actions.users.authenticate_user import AuthenticateUserAction
from huesoporro.actions.users.get_user_by_jwt import GetUserByJWTAction
from huesoporro.actions.users.refresh_user_jwt import RefreshUserJwtAction
from huesoporro.infra.authenticator import TwitchAuthenticator
from huesoporro.models import Quote, TwitchAuth
from huesoporro.svc.users_svcs import IsValidTokenSvc, RefreshTokenSvc


class AsyncMock(MagicMock):
    async def __call__(self, *args, **kwargs):
        return super().__call__(*args, **kwargs)


@pytest.fixture
def twitch_authenticator(s):
    return TwitchAuthenticator(s=s)


@pytest.fixture
def fake_twitch_authenticator():
    return AsyncMock()


@pytest.fixture
def refresh_token_svc(twitch_authenticator, fake_twitch_authenticator, s):
    svc = RefreshTokenSvc(twitch_authenticator=twitch_authenticator)
    svc.twitch_authenticator = fake_twitch_authenticator
    return svc


@pytest.fixture
def is_valid_svc(twitch_authenticator, fake_twitch_authenticator):
    svc = IsValidTokenSvc(authenticator=twitch_authenticator)
    svc.authenticator = fake_twitch_authenticator
    return svc


@pytest.fixture
def get_user_by_jwt_action(
    s, get_user_by_username_svc, update_user_svc, is_valid_svc, refresh_token_svc
):
    return GetUserByJWTAction(
        get_user_by_username_svc=get_user_by_username_svc,
        update_user_svc=update_user_svc,
        refresh_token_svc=refresh_token_svc,
        is_valid_token_svc=is_valid_svc,
        s=s,
    )


@pytest.fixture
def refresh_user_jwt_action(
    s, get_user_by_username_svc, update_user_svc, is_valid_svc, refresh_token_svc
):
    return RefreshUserJwtAction(
        get_user_by_username_svc=get_user_by_username_svc,
        update_user_svc=update_user_svc,
        refresh_token_svc=refresh_token_svc,
        is_valid_token_svc=is_valid_svc,
        s=s,
    )


@pytest.fixture
def authenticate_user_action(  # noqa: PLR0913
    s,
    get_user_by_username_svc,
    get_twitch_auth_by_auth_code_svc,
    update_user_svc,
    create_user_svc,
    twitch_authenticator,
):
    return AuthenticateUserAction(
        get_user_by_username_svc=get_user_by_username_svc,
        update_user_svc=update_user_svc,
        create_user_svc=create_user_svc,
        get_tokens_by_auth_code_svc=get_twitch_auth_by_auth_code_svc,
        s=s,
    )


@pytest.fixture
def get_random_quote_action(get_random_quote_svc):
    return GetRandomQuoteAction(get_random_quote_svc=get_random_quote_svc)


@pytest.fixture
def create_random_quote_action(create_quote_svc, is_mod_svc):
    return CreateQuoteAction(create_quote_svc=create_quote_svc, is_mod_svc=is_mod_svc)


async def test_get_user_by_jwt_action_raises_value_error(
    get_user_by_jwt_action: GetUserByJWTAction, user, s
):
    with pytest.raises(ValueError, match=f"User {user.username} not found"):
        await get_user_by_jwt_action.run(jwt_token=user.encode(settings=s))


async def test_get_user_by_jwt_returns_user(
    get_user_by_jwt_action: GetUserByJWTAction,
    persisted_user,
    s,
    fake_twitch_authenticator,
):
    fake_twitch_authenticator.token_is_valid.return_value = True
    jwt = persisted_user.encode(settings=s)
    assert await get_user_by_jwt_action.run(jwt_token=jwt)


async def test_get_user_by_jwt_returns_refreshed_user(
    get_user_by_jwt_action: GetUserByJWTAction,
    persisted_user,
    s,
    fake_twitch_authenticator,
):
    jwt = persisted_user.encode(settings=s)
    fake_twitch_authenticator.token_is_valid.return_value = False
    fake_twitch_authenticator.refresh_token.return_value = TwitchAuth(
        access_token="mocked",  # noqa: S106
        refresh_token="mocked",  # noqa: S106
        userinfo={},
    )
    assert await get_user_by_jwt_action.run(jwt_token=jwt)


async def test_refresh_user_jwt_returns_none_on_valid_token(
    refresh_user_jwt_action: RefreshUserJwtAction,
    persisted_user,
    s,
    fake_twitch_authenticator,
):
    fake_twitch_authenticator.token_is_valid.return_value = True
    assert await refresh_user_jwt_action.run(user=persisted_user) is None


async def test_refresh_user_jwt_returns_updated_user(
    refresh_user_jwt_action: RefreshUserJwtAction,
    persisted_user,
    s,
    fake_twitch_authenticator,
):
    fake_twitch_authenticator.token_is_valid.return_value = False
    fake_twitch_authenticator.refresh_token.return_value = TwitchAuth(
        access_token="mocked",  # noqa: S106
        refresh_token="mocked",  # noqa: S106
        userinfo={},
    )
    user = await refresh_user_jwt_action.run(user=persisted_user)
    assert user
    assert user.twitch_access_token == "mocked"  # noqa: S105
    assert user.twitch_refresh_token == "mocked"  # noqa: S105


async def test_authenticate_existing_user_returns_user(
    persisted_user, authenticate_user_action, fake_twitch_authenticator
):
    fake_twitch_authenticator.get_token.return_value = TwitchAuth(
        access_token="mocked",  # noqa: S106
        refresh_token="mocked",  # noqa: S106
        userinfo={"preferred_username": persisted_user.username},
    )
    user = await authenticate_user_action.run(auth_code="mocked")
    assert user.id == persisted_user.id
    assert user.username == persisted_user.username
    assert user.twitch_access_token == "mocked" != persisted_user.twitch_access_token  # noqa: S105
    assert (
        user.twitch_refresh_token
        == "mocked"  # noqa: S105
        != persisted_user.external_auth["twitch"].refresh_token
    )


async def test_authenticate_raises_value_error_on_not_allowed_user(
    authenticate_user_action, fake_twitch_authenticator
):
    fake_twitch_authenticator.get_token.return_value = TwitchAuth(
        access_token="mocked",  # noqa: S106
        refresh_token="mocked",  # noqa: S106
        userinfo={"preferred_username": "not_allowed"},
    )
    with pytest.raises(ValueError, match="User not_allowed is not allowed"):
        await authenticate_user_action.run(auth_code="mocked")


async def test_authenticate_user(
    authenticate_user_action, fake_twitch_authenticator, user
):
    fake_twitch_authenticator.get_token.return_value = TwitchAuth(
        access_token="mocked",  # noqa: S106
        refresh_token="mocked",  # noqa: S106
        userinfo={"preferred_username": user.username},
    )
    user = await authenticate_user_action.run(auth_code="mocked")
    assert user.username == user.username


async def test_get_random_quote_action(
    get_random_quote_action: GetRandomQuoteAction, persisted_quote: Quote
):
    assert (
        await get_random_quote_action.run(persisted_quote.channel_name)
        == persisted_quote
    )


async def test_create_quote_action_returns_none_on_not_mod_user(
    create_random_quote_action: CreateQuoteAction, user
):
    assert (
        await create_random_quote_action.run(
            user=user,
            channel=user.username,
            quote="mocked",
            author="mocked",
            username="mocked",
        )
        is None
    )


async def test_create_quote_action(
    create_random_quote_action: CreateQuoteAction, user, quote: Quote, persisted_user
):
    quote.channel_name = persisted_user.username

    new_quote = await create_random_quote_action.run(
        user=persisted_user,
        username=user.username,
        channel=user.username,
        quote=quote.quote,
        is_active=quote.is_active,
        author=quote.author,
    )
    assert new_quote
    quote.id = new_quote.id
    quote.created_at = new_quote.created_at
    quote.last_updated_at = new_quote.last_updated_at
    assert quote == new_quote
