from pathlib import Path
from uuid import UUID

import pytest
from caribou.migrate import Database as CaribouDatabase
from caribou.migrate import load_migrations
from polyfactory.factories.pydantic_factory import ModelFactory
from polyfactory.pytest_plugin import register_fixture
from pydantic import BaseModel

from huesoporro.infra.repos import ChatbotRepo, IRepo, QuoteRepo, UserRepo
from huesoporro.models import Chatbot, Quote, TwitchAuth, User
from huesoporro.settings import Settings
from huesoporro.svc.backoff_service import BackoffService
from huesoporro.svc.chatbot_svcs import (
    CreateChatbotSvc,
    GetChatbotByUserIdSvc,
    UpdateChatbotSvc,
)
from huesoporro.svc.hello import HelloGeneratorSvc
from huesoporro.svc.is_mod import IsModSvc
from huesoporro.svc.quotes_svcs import CreateQuoteSvc, GetRandomQuoteSvc
from huesoporro.svc.users_svcs import (
    CreateUserSvc,
    DeleteUserSvc,
    GetTwitchAuthByAuthCodeSvc,
    GetUserByIdSvc,
    GetUserByUsernameSvc,
    UpdateUserSvc,
)


@register_fixture()
class TwitchAuthFactory(ModelFactory[TwitchAuth]): ...


@register_fixture()
class ChatbotFactory(ModelFactory[Chatbot]): ...


@register_fixture()
class UserFactory(ModelFactory[User]): ...


@register_fixture()
class QuoteFactory(ModelFactory[Quote]): ...


@pytest.fixture
def s(tmp_path: Path, user: User) -> Settings:
    return Settings(
        static_files_path=tmp_path / "static_files",
        db_filepath=tmp_path / "huesoporro.db",
        twitch_client_id="test_client_id",
        twitch_client_secret="test_client_secret",  # type: ignore[arg-type]  # noqa: S106
        jwt_secret="test_jwt_secret",  # type: ignore[arg-type]  # noqa: S106
        allowed_users=[user.username],
    )


@pytest.fixture
def db(s, cdb):
    class BogusRepo(IRepo[BaseModel]):
        async def get_by_id(self, obj_id: UUID, auto_commit=True) -> BaseModel | None:
            pass

        async def list(  # type: ignore[empty-body]
            self, offset: int = 0, limit: int = 10, auto_commit=True
        ) -> list[BaseModel]:
            pass

        async def create(self, obj: BaseModel, auto_commit: bool = True) -> BaseModel:  # type: ignore[empty-body]
            pass

        async def update(self, obj: BaseModel, auto_commit=True) -> BaseModel:  # type: ignore[empty-body]
            pass

        async def delete(self, obj: BaseModel, auto_commit=True) -> None:  # type: ignore[empty-body]
            pass

        async def get_by_user(self, user: str) -> BaseModel:  # type: ignore[empty-body]
            pass

    return BogusRepo(s=s).get_client


@pytest.fixture
def cdb(s) -> CaribouDatabase:
    cdb = CaribouDatabase(
        db_url=s.db_filepath,
    )
    cdb.initialize_version_control()
    migrations = load_migrations(Path(__file__).parents[1] / "migrations")
    cdb.upgrade(migrations)
    return cdb


@pytest.fixture
async def user_repo(s, db):
    return UserRepo(s=s)


@pytest.fixture
def twitch_auth(twitch_auth_factory):
    return twitch_auth_factory.build(
        userinfo={
            "aud": twitch_auth_factory.__faker__.pystr(),
            "exp": twitch_auth_factory.__faker__.pyint(),
            "iat": twitch_auth_factory.__faker__.pyint(),
            "iss": "https://id.twitch.tv/oauth2",
            "sub": twitch_auth_factory.__faker__.pystr(),
            "azp": twitch_auth_factory.__faker__.pystr(),
            "preferred_username": twitch_auth_factory.__faker__.user_name(),
        }
    )


@pytest.fixture
async def user(user_factory: UserFactory, twitch_auth) -> User:
    return user_factory.build(external_auth={"twitch": twitch_auth})


@pytest.fixture
async def persisted_user(user_repo: UserRepo, user: User) -> User:
    return await user_repo.create(user)


@pytest.fixture
def create_user_svc(user_repo):
    return CreateUserSvc(user_repo=user_repo)


@pytest.fixture
def update_user_svc(user_repo):
    return UpdateUserSvc(user_repo=user_repo)


@pytest.fixture
def delete_user_svc(user_repo):
    return DeleteUserSvc(user_repo=user_repo)


@pytest.fixture
def get_user_by_id_svc(user_repo):
    return GetUserByIdSvc(user_repo=user_repo)


@pytest.fixture
def get_user_by_username_svc(user_repo):
    return GetUserByUsernameSvc(user_repo=user_repo)


@pytest.fixture
def get_twitch_auth_by_auth_code_svc(
    twitch_authenticator, fake_twitch_authenticator, user_repo, s
):
    svc = GetTwitchAuthByAuthCodeSvc(authenticator=twitch_authenticator, s=s)
    svc.authenticator = fake_twitch_authenticator
    return svc


@pytest.fixture
async def chatbot_repo(s, db):
    return ChatbotRepo(s=s)


@pytest.fixture
async def chatbot(chatbot_factory, user):
    return chatbot_factory.build(user_id=user.id)


@pytest.fixture
async def five_chatbots(chatbot_factory, user):
    return [chatbot_factory.build() for _ in range(5)]


@pytest.fixture
async def persisted_five_chatbots(five_chatbots, chatbot_repo):
    return [await chatbot_repo.create(chatbot) for chatbot in five_chatbots]


@pytest.fixture
async def persisted_chatbot(chatbot_repo, chatbot, persisted_user):
    return await chatbot_repo.create(chatbot)


@pytest.fixture
async def create_chatbot_svc(chatbot_repo):
    return CreateChatbotSvc(repo=chatbot_repo)


@pytest.fixture
async def get_chatbot_by_user_id_svc(chatbot_repo):
    return GetChatbotByUserIdSvc(repo=chatbot_repo)


@pytest.fixture
async def update_chatbot_svc(chatbot_repo):
    return UpdateChatbotSvc(repo=chatbot_repo)


@pytest.fixture
async def is_mod_svc(chatbot_repo):
    return IsModSvc(repo=chatbot_repo)


@pytest.fixture
async def quote_repo(s, db):
    return QuoteRepo(s=s)


@pytest.fixture
async def quote(quote_factory):
    return quote_factory.build()


@pytest.fixture
async def five_quotes(quote_factory):
    return [quote_factory.build() for _ in range(5)]


@pytest.fixture
async def persisted_quote(quote_repo, quote):
    quote.is_active = True
    return await quote_repo.create(quote)


@pytest.fixture
async def persisted_five_quotes(five_quotes, quote_repo):
    return [await quote_repo.create(quote) for quote in five_quotes]


@pytest.fixture
async def create_quote_svc(quote_repo):
    return CreateQuoteSvc(repo=quote_repo)


@pytest.fixture
async def get_random_quote_svc(quote_repo):
    return GetRandomQuoteSvc(repo=quote_repo)


@pytest.fixture
def backoff_callable():
    def foo():
        return "foo"

    return foo


@pytest.fixture
def async_backoff_callable():
    async def async_foo():
        return "async foo"

    return async_foo


@pytest.fixture
async def backoff_svc(backoff_callable, async_backoff_callable):
    backoff_svc = BackoffService()
    backoff_svc.add_callable(backoff_callable, 3)
    backoff_svc.add_callable(async_backoff_callable, 3)
    return backoff_svc


@pytest.fixture
async def hello_generator_svc():
    return HelloGeneratorSvc()
