from typing import Any
import advanced_alchemy
import bcrypt
import jwt
from advanced_alchemy.repository import SQLAlchemyAsyncRepository
from advanced_alchemy.service import SQLAlchemyAsyncRepositoryService
from uuid import UUID
import msgspec
from sqlalchemy.ext.asyncio import AsyncSession

from core.settings import settings
from core.mail import MailClient
from .models import PermissionModel, RoleModel, UserModel

__all__ = [
    "UserService",
    "AuthService",
    "PermissionService",
    "RoleService",
    "LoginUserDTO",
    "SignupUserDTO",
    "provide_user_service",
    "provide_auth_service",
    "provide_permission_service",
    "provide_role_service",
    "InvalidPasswordError",
    "InvalidEmailError",
    "DecodeTokenError",
]


class InvalidPasswordError(Exception):
    pass


class InvalidEmailError(Exception):
    pass


class DecodeTokenError(Exception):
    pass


class LoginUserDTO(msgspec.Struct):
    email: str
    password: str


class SignupUserDTO(msgspec.Struct):
    email: str
    password: str


class AccountDTO(msgspec.Struct):
    id: UUID
    email: str


class UserService(SQLAlchemyAsyncRepositoryService):
    class Repository(SQLAlchemyAsyncRepository[UserModel]):
        model_type = UserModel

    repository_type = Repository


class RoleService(SQLAlchemyAsyncRepositoryService):
    class Repository(SQLAlchemyAsyncRepository[RoleModel]):
        model_type = RoleModel

    repository_type = Repository


class PermissionService(SQLAlchemyAsyncRepositoryService):
    class Repository(SQLAlchemyAsyncRepository[PermissionModel]):
        model_type = PermissionModel

    repository_type = Repository


class AuthService:
    def __init__(self, session: AsyncSession, mail_client: MailClient, user_service: UserService):
        self.session = session
        self.mail_client = mail_client
        self.user_service = user_service

    class Repository(SQLAlchemyAsyncRepository[UserModel]):
        model_type = UserModel

    repository_type = Repository

    async def signup(self, user: SignupUserDTO) -> UserModel:
        self.mail_client.send([user.email], "Sign up", "sign up info code")
        return await self.user_service.create(
            UserModel(
                email=user.email,
                password=self._hash_password(user.password),
                is_email_verified=False,
                is_enabled=True,
            ),
            auto_commit=True,
        )

    async def login(self, user: LoginUserDTO) -> str:
        try:
            login_user = await self.user_service.get_one(UserModel.email == user.email)
        except advanced_alchemy.exceptions.NotFoundError:
            raise InvalidEmailError(f"Email {user.email} not found")

        if not self._check_password(user.password, login_user.password):
            raise InvalidPasswordError

        return self._encode_token(
            AccountDTO(
                id=login_user.id,
                email=login_user.email,
            )
        )

    @staticmethod
    def _check_password(password: str, hashed_password: str) -> bool:
        return bcrypt.checkpw(password.encode("utf-8"), hashed_password.encode("utf-8"))

    @staticmethod
    def _hash_password(password: str) -> str:
        return bcrypt.hashpw(password.encode("utf8"), bcrypt.gensalt()).decode("utf8")

    async def get_account(self, token: str) -> AccountDTO:
        try:
            account_data = self._decode_token(token)
            return AccountDTO(**account_data)
        except Exception:
            raise DecodeTokenError

    def _encode_token(self, account: AccountDTO) -> str:
        return jwt.encode(msgspec.to_builtins(account), settings.jwt_secret, algorithm="HS256")

    def _decode_token(self, token: str) -> dict[str, Any]:
        return jwt.decode(token, settings.jwt_secret, algorithms=["HS256"])


async def provide_user_service(db_session: AsyncSession) -> UserService:
    return UserService(session=db_session)


async def provide_role_service(db_session: AsyncSession) -> RoleService:
    return RoleService(session=db_session)


async def provide_permission_service(db_session: AsyncSession) -> PermissionService:
    return PermissionService(session=db_session)


async def provide_auth_service(
    db_session: AsyncSession,
    mail_client: MailClient,
    user_service: UserService,
) -> AuthService:
    return AuthService(session=db_session, mail_client=mail_client, user_service=user_service)
