import datetime
from typing import Any, Optional

from common.database import DBConfig, Db
from common.database.exceptions import UniqueConstraintError
from common.logging import get_logger, span
from common.models.common import (
    UserAccessToken,
    Platform,
    UserAccessTokenCreate,
    UserAccessTokenUpdate,
)
from common.models.user import (
    SocialMediaAccount,
    SocialMediaAccountCreate,
    User,
    UserCreate,
    UserUpdate,
    TwitchAccount,
    TwitchAccountCreate,
    TwitchAccountUpdate,
    KickAccount,
    KickAccountCreate,
    KickAccountUpdate,
)
from common.utils.crypto import Crypto, CryptoConfig

USER_COLUMNS_LEN = 13
USER_OAUTH_COLUMNS_LEN = 11
KICK_ACCOUNT_COLUMNS_LEN = 8
TWITCH_ACCOUNT_COLUMNS_LEN = 8
KICK_ACCOUNT_OAUTH_COLUMNS_LEN = 10
TWITCH_ACCOUNT_OAUTH_COLUMNS_LEN = 10

SELECT_ONLY_USER_QUERY = """
                         SELECT u.id,
                                u.first_promoter_id,
                                u.first_name,
                                u.last_name,
                                u.email,
                                u.phone,
                                u.tier,
                                u.product,
                                u.created_at,
                                u.updated_at,
                                u.deleted_at,
                                u.last_login,
                                u.customer_journey_completed_at
                         FROM users u \
                         """

SELECT_USER_QUERY = """
                    SELECT u.id,
                           u.first_promoter_id,
                           u.first_name,
                           u.last_name,
                           u.email,
                           u.phone,
                           u.tier,
                           u.product,
                           u.created_at,
                           u.updated_at,
                           u.deleted_at,
                           u.last_login,
                           u.customer_journey_completed_at,

                           -- Twitch account
                           ta.id                   AS twitch_account_id,
                           ta.twitch_id,
                           ta.twitch_username,
                           ta.profile_link         AS twitch_profile_link,
                           ta.profile_picture      AS twitch_profile_picture,
                           ta.created_at           AS twitch_created_at,
                           ta.updated_at           AS twitch_updated_at,
                           ta.deleted_at           AS twitch_deleted_at,

                           -- Kick account
                           ka.id                   AS kick_account_id,
                           ka.kick_id,
                           ka.kick_username,
                           ka.profile_link         AS kick_profile_link,
                           ka.profile_picture      AS kick_profile_picture,
                           ka.created_at           AS kick_created_at,
                           ka.updated_at           AS kick_updated_at,
                           ka.deleted_at           AS kick_deleted_at,

                           -- Twitch OAuth token
                           ut_twitch.id            AS twitch_oauth_id,
                           ut_twitch.platform      AS twitch_platform,
                           ut_twitch.access_token  AS twitch_access_token,
                           ut_twitch.refresh_token AS twitch_refresh_token,
                           ut_twitch.token_type    AS twitch_token_type,
                           ut_twitch.expires_in    AS twitch_expires_in,
                           ut_twitch.scope         AS twitch_scope,
                           ut_twitch.created_at    AS twitch_oauth_created_at,
                           ut_twitch.updated_at    AS twitch_oauth_updated_at,
                           ut_twitch.deleted_at    AS twitch_oauth_deleted_at,

                           -- Kick OAuth token
                           ut_kick.id              AS kick_oauth_id,
                           ut_kick.platform        AS kick_platform,
                           ut_kick.access_token    AS kick_access_token,
                           ut_kick.refresh_token   AS kick_refresh_token,
                           ut_kick.token_type      AS kick_token_type,
                           ut_kick.expires_in      AS kick_expires_in,
                           ut_kick.scope           AS kick_scope,
                           ut_kick.created_at      AS kick_oauth_created_at,
                           ut_kick.updated_at      AS kick_oauth_updated_at,
                           ut_kick.deleted_at      AS kick_oauth_deleted_at,

                           (
                                SELECT array_agg(DISTINCT ut.platform)
                                FROM user_oauth_tokens ut
                                WHERE ut.user_id = u.id
                                AND ut.platform NOT IN ('twitch', 'kick')
                            ) AS social_media_platforms
                    FROM users u
                             LEFT JOIN twitch_account ta
                                       ON u.id = ta.user_id
                             LEFT JOIN kick_account ka
                                       ON u.id = ka.user_id
                             LEFT JOIN user_oauth_tokens ut_twitch
                                       ON u.id = ut_twitch.user_id
                                           AND ut_twitch.platform = 'twitch'
                             LEFT JOIN user_oauth_tokens ut_kick
                                       ON u.id = ut_kick.user_id
                                           AND ut_kick.platform = 'kick'
                    """

SELECT_USER_BY_ID_QUERY = SELECT_USER_QUERY + "WHERE u.id = %s AND u.deleted_at IS NULL"
SELECT_USER_BY_EMAIL_QUERY = (
    SELECT_USER_QUERY + "WHERE u.email = %s AND u.deleted_at IS NULL"
)
SELECT_USER_BY_TWITCH_USERNAME_QUERY = (
    SELECT_USER_QUERY + "WHERE ta.twitch_username = %s AND u.deleted_at IS NULL"
)
SELECT_USER_BY_TWITCH_ID_QUERY = (
    SELECT_USER_QUERY + "WHERE ta.twitch_id = %s AND u.deleted_at IS NULL"
)
SELECT_USER_BY_KICK_USERNAME_QUERY = (
    SELECT_USER_QUERY + "WHERE ka.kick_username = %s AND u.deleted_at IS NULL"
)
SELECT_USER_BY_KICK_ID_QUERY = (
    SELECT_USER_QUERY + "WHERE ka.kick_id = %s AND u.deleted_at IS NULL"
)
SELECT_OAUTH_TOKEN_QUERY = """
                           SELECT id,
                                  user_id,
                                  platform,
                                  access_token,
                                  refresh_token,
                                  token_type,
                                  expires_in,
                                  scope,
                                  created_at,
                                  updated_at,
                                  deleted_at
                           FROM user_oauth_tokens
                           """

SELECT_TWITCH_ACCOUNT_QUERY = """
                              SELECT id,
                                     user_id,
                                     twitch_id,
                                     twitch_username,
                                     profile_link,
                                     profile_picture,
                                     created_at,
                                     updated_at,
                                     deleted_at
                              FROM twitch_account \
                              """

SELECT_KICK_ACCOUNT_QUERY = """
                            SELECT id,
                                   user_id,
                                   kick_id,
                                   kick_username,
                                   profile_link,
                                   profile_picture,
                                   created_at,
                                   updated_at,
                                   deleted_at
                            FROM kick_account \
                            """

logger = get_logger(__name__)


class UserNotFoundError(Exception):
    """Exception raised when a user is not found."""

    def __init__(self, message="User not found"):
        self.message = message
        super().__init__(self.message)


class UserAlreadyExistsError(Exception):
    """Exception raised when trying to create a user that already exists."""

    def __init__(self, message="User already exists"):
        self.message = message
        super().__init__(self.message)


class UserDAO:
    """Data Access Object for users in PostgreSQL."""

    def __init__(self, db: Db | None = None, crypto: Crypto | None = None):
        """Initialize with an existing DB connection or create a new one."""
        self.db = db if db is not None else Db(DBConfig())
        self.crypto = (
            crypto if crypto is not None else Crypto(config=CryptoConfig())  # type: ignore[missing-argument]
        )

    async def _upsert_owned_account(
        self,
        *,
        table: str,
        columns: list[str],
        values: tuple[Any, ...],
        conflict_column: str,
        update_columns: list[str],
    ) -> int:
        """Upsert an account row only when the conflicting provider id belongs to the same user."""

        placeholders = ", ".join(["%s"] * len(columns))
        columns_formatted = ", ".join(columns)
        update_clause = ", ".join(
            f"{column} = EXCLUDED.{column}" for column in update_columns
        )
        query = f"""
            INSERT INTO {table} ({columns_formatted})
            VALUES ({placeholders})
            ON CONFLICT ({conflict_column})
            DO UPDATE SET {update_clause}
            WHERE {table}.user_id = EXCLUDED.user_id
            RETURNING id;
        """

        async with await self.db.get_connection() as conn:
            lock = conn._get_lock()
            async with lock:
                async with conn.cursor() as cur:
                    await cur.execute(query.encode(), values)
                    result = await cur.fetchone()
                    if not result:
                        raise UniqueConstraintError(
                            f"{table} is already linked to another user."
                        )

            await conn.commit()

        return int(result[0])

    async def create_user(self, user: UserCreate) -> Optional[User]:
        """Create a new user."""
        with span(
            logger,
            "create_user",
            {"email": str(user.email)},
            log_entry=False,
            log_exit=False,
        ):
            try:
                # Check if user already exists
                try:
                    await self.get_user_by_email(user.email.__str__())
                    logger.warning(
                        f"User with email {user.email} already exists",
                        extra={"email": str(user.email)},
                    )
                    raise UserAlreadyExistsError(
                        f"User with email {user.email} already exists."
                    )
                except UserNotFoundError:
                    # This is the expected path when creating a new user
                    pass

                user_dict = user.dict()
                columns = []
                values = []
                placeholders = []

                for key, value in user_dict.items():
                    if value is not None:
                        columns.append(key)
                        values.append(value)
                        placeholders.append("%s")

                result = await self.db.insert(
                    table="users", columns=columns, values=tuple(values)
                )

                if result:
                    new_user = User(
                        id=result,
                        created_at=datetime.datetime.now(),
                        updated_at=datetime.datetime.now(),
                        **user_dict,
                    )
                    logger.info(
                        f"Created new user with ID {result}",
                        extra={"user_id": result, "email": str(user.email)},
                    )
                    return new_user

                logger.error(
                    f"Failed to create user with email {user.email}",
                    extra={"email": str(user.email)},
                )
                return None
            except Exception as e:
                if not isinstance(e, UserAlreadyExistsError):
                    logger.error(
                        f"Error creating user: {e}",
                        extra={"email": str(user.email), "error": str(e)},
                    )
                raise

    async def get_user_by_id(self, user_id: int) -> User:
        """Get a user by their ID."""
        with span(
            logger,
            "get_user_by_id",
            {"user_id": user_id},
            log_entry=False,
            log_exit=False,
        ):
            try:
                row = await self.db.fetch_one(SELECT_USER_BY_ID_QUERY, (user_id,))
                if not row:
                    logger.warning(
                        f"User with ID {user_id} not found", extra={"user_id": user_id}
                    )
                    raise UserNotFoundError(f"User with ID {user_id} not found.")

                user = self._row_to_user(row)
                logger.debug(
                    f"Found user with ID {user_id}",
                    extra={"user_id": user_id, "email": str(user.email)},
                )
                return user
            except Exception as e:
                if not isinstance(e, UserNotFoundError):
                    logger.error(
                        f"Error retrieving user with ID {user_id}: {e}",
                        extra={"user_id": user_id, "error": str(e)},
                    )
                raise

    async def get_user_by_email(self, email: str, **kwargs) -> User:
        """Get a user by their email."""
        with span(
            logger,
            "get_user_by_email",
            {"email": email, "only_user": kwargs.get("only_user", False)},
            log_entry=False,
            log_exit=False,
        ):
            try:
                if kwargs.get("only_user", False):
                    row = await self.db.fetch_one(
                        SELECT_ONLY_USER_QUERY + "WHERE email = %s", (email,)
                    )
                    if not row:
                        logger.warning(
                            f"User with email {email} not found", extra={"email": email}
                        )
                        raise UserNotFoundError(f"User with email {email} not found.")

                    user = self._row_to_user(row)
                    logger.debug(
                        f"Found user with email {email} (only user data)",
                        extra={"email": email, "user_id": user.id},
                    )
                    return user

                row = await self.db.fetch_one(SELECT_USER_BY_EMAIL_QUERY, (email,))
                if not row:
                    logger.warning(
                        f"User with email {email} not found", extra={"email": email}
                    )
                    raise UserNotFoundError(f"User with email {email} not found.")

                user = self._row_to_user(row)
                logger.debug(
                    f"Found user with email {email}",
                    extra={"email": email, "user_id": user.id},
                )
                return user
            except Exception as e:
                if not isinstance(e, UserNotFoundError):
                    logger.error(
                        f"Error retrieving user with email {email}: {e}",
                        extra={"email": email, "error": str(e)},
                    )
                raise

    async def get_user_by_twitch_username(self, username: str) -> User:
        """Get a user by their Twitch username."""
        with span(
            logger,
            "get_user_by_twitch_username",
            {"twitch_username": username},
            log_entry=False,
            log_exit=False,
        ):
            try:
                normalized_username = username.lower() if username else ""
                row = await self.db.fetch_one(
                    SELECT_USER_BY_TWITCH_USERNAME_QUERY, (normalized_username,)
                )
                if not row:
                    logger.warning(
                        f"User with Twitch username {username} not found",
                        extra={"twitch_username": username},
                    )
                    raise UserNotFoundError(
                        f"User with Twitch username {username} not found."
                    )

                user = self._row_to_user(row)
                logger.debug(
                    f"Found user with Twitch username {username}",
                    extra={"twitch_username": username, "user_id": user.id},
                )
                return user
            except Exception as e:
                if not isinstance(e, UserNotFoundError):
                    logger.error(
                        f"Error retrieving user with Twitch username {username}: {e}",
                        extra={"twitch_username": username, "error": str(e)},
                    )
                raise

    async def get_user_by_twitch_id(self, twitch_id: str) -> User:
        """Get a user by their Twitch broadcast ID."""
        with span(
            logger,
            "get_user_by_twitch_id",
            {"twitch_id": twitch_id},
            log_entry=False,
            log_exit=False,
        ):
            try:
                row = await self.db.fetch_one(
                    SELECT_USER_BY_TWITCH_ID_QUERY, (twitch_id,)
                )
                if not row:
                    logger.warning(
                        f"User with Twitch ID {twitch_id} not found",
                        extra={"twitch_id": twitch_id},
                    )
                    raise UserNotFoundError(
                        f"User with Twitch ID {twitch_id} not found."
                    )

                user = self._row_to_user(row)
                logger.debug(
                    f"Found user with Twitch ID {twitch_id}",
                    extra={"twitch_id": twitch_id, "user_id": user.id},
                )
                return user
            except Exception as e:
                if not isinstance(e, UserNotFoundError):
                    logger.error(
                        f"Error retrieving user with Twitch ID {twitch_id}: {e}",
                        extra={"twitch_id": twitch_id, "error": str(e)},
                    )
                raise

    async def get_user_by_kick_username(self, username: str) -> User:
        """Get a user by their Kick username."""
        with span(
            logger,
            "get_user_by_kick_username",
            {"kick_username": username},
            log_entry=False,
            log_exit=False,
        ):
            try:
                normalized_username = username.lower() if username else ""
                row = await self.db.fetch_one(
                    SELECT_USER_BY_KICK_USERNAME_QUERY, (normalized_username,)
                )
                if not row:
                    logger.warning(
                        f"User with Kick username {username} not found",
                        extra={"kick_username": username},
                    )
                    raise UserNotFoundError(
                        f"User with Kick username {username} not found."
                    )

                user = self._row_to_user(row)
                logger.debug(
                    f"Found user with Kick username {username}",
                    extra={"kick_username": username, "user_id": user.id},
                )
                return user
            except Exception as e:
                if not isinstance(e, UserNotFoundError):
                    logger.error(
                        f"Error retrieving user with Kick username {username}: {e}",
                        extra={"kick_username": username, "error": str(e)},
                    )
                raise

    async def get_user_by_kick_id(self, kick_id: str) -> User:
        """Get a user by their Kick broadcast ID."""
        with span(
            logger,
            "get_user_by_kick_id",
            {"kick_id": kick_id},
            log_entry=False,
            log_exit=False,
        ):
            try:
                row = await self.db.fetch_one(SELECT_USER_BY_KICK_ID_QUERY, (kick_id,))
                if not row:
                    logger.warning(
                        f"User with Kick ID {kick_id} not found",
                        extra={"kick_id": kick_id},
                    )
                    raise UserNotFoundError(f"User with Kick ID {kick_id} not found.")

                user = self._row_to_user(row)
                logger.debug(
                    f"Found user with Kick ID {kick_id}",
                    extra={"kick_id": kick_id, "user_id": user.id},
                )
                return user
            except Exception as e:
                if not isinstance(e, UserNotFoundError):
                    logger.error(
                        f"Error retrieving user with Kick ID {kick_id}: {e}",
                        extra={"kick_id": kick_id, "error": str(e)},
                    )
                raise

    async def update_user_by_email(self, email: str, update: UserUpdate) -> None:
        """Update a user's details."""
        with span(logger, "update_user_by_email", {"email": email}):
            update_dict = update.dict(exclude_none=True)
            if not update_dict:
                logger.debug(
                    f"No updates provided for user email {email}, skipping",
                    extra={"email": email},
                )
                return

            set_clauses = []
            values = []

            for key, value in update_dict.items():
                set_clauses.append(key)
                values.append(value)

            try:
                await self.db.update(
                    table="users",
                    set_columns=set_clauses,
                    set_values=values,
                    where_clause="email = %s",
                    where_params=(email,),
                )
                logger.info(
                    f"Updated user email {email} with fields: {list(update_dict.keys())}",
                    extra={"email": email, "fields": list(update_dict.keys())},
                )
            except Exception as e:
                logger.error(
                    f"Error updating user email {email}: {e}",
                    extra={"email": email, "error": str(e)},
                )
                raise

    async def update_user(self, user_id: int, update: UserUpdate) -> None:
        """Update a user's details."""
        with span(
            logger, "update_user", {"user_id": user_id}, log_entry=False, log_exit=False
        ):
            update_dict = update.dict(exclude_none=True)
            if not update_dict:
                logger.debug(
                    f"No updates provided for user ID {user_id}, skipping",
                    extra={"user_id": user_id},
                )
                return

            set_clauses = []
            values = []

            for key, value in update_dict.items():
                set_clauses.append(key)
                values.append(value)

            try:
                await self.db.update(
                    table="users",
                    set_columns=set_clauses,
                    set_values=values,
                    where_clause="id = %s",
                    where_params=(user_id,),
                )
                logger.info(
                    f"Updated user ID {user_id} with fields: {list(update_dict.keys())}",
                    extra={"user_id": user_id, "fields": list(update_dict.keys())},
                )
            except Exception as e:
                logger.error(
                    f"Error updating user ID {user_id}: {e}",
                    extra={"user_id": user_id, "error": str(e)},
                )
                raise

    async def delete_user(self, user_id: int) -> None:
        """Delete a user."""
        await self.db.delete(
            table="users", where_clause="id = %s", where_params=(user_id,)
        )

    async def get_twitch_account(self, user_id: int) -> Optional[TwitchAccount]:
        """
        Fetch a user's Twitch account details.
        """
        row = await self.db.fetch_one(
            SELECT_TWITCH_ACCOUNT_QUERY + "WHERE user_id = %s",
            (user_id,),
        )

        if not row:
            raise Exception("Twitch account not found for user.")

        return TwitchAccount(
            id=row[0],
            user_id=row[1],
            twitch_id=row[2],
            twitch_username=row[3],
            profile_link=row[4],
            profile_picture=row[5],
            created_at=row[6],
            updated_at=row[7],
            deleted_at=row[8],
        )

    async def get_kick_account(self, user_id: int) -> Optional[KickAccount]:
        """
        Fetch a user's Kick account details.
        """
        row = await self.db.fetch_one(
            SELECT_KICK_ACCOUNT_QUERY + "WHERE user_id = %s",
            (user_id,),
        )

        if not row:
            raise Exception("Kick account not found for user.")

        return KickAccount(
            id=row[0],
            user_id=row[1],
            kick_id=row[2],
            kick_username=row[3],
            profile_link=row[4],
            profile_picture=row[5],
            created_at=row[6],
            updated_at=row[7],
            deleted_at=row[8],
        )

    async def get_twitch_account_by_username(
        self, username: str
    ) -> Optional[TwitchAccount]:
        """
        Fetch a user's Twitch account details by username.
        """
        normalized_username = username.lower() if username else ""
        row = await self.db.fetch_one(
            SELECT_TWITCH_ACCOUNT_QUERY + "WHERE twitch_username = %s",
            (normalized_username,),
        )

        if not row:
            raise Exception("Twitch account not found for user.")

        return TwitchAccount(
            id=row[0],
            user_id=row[1],
            twitch_id=row[2],
            twitch_username=row[3],
            profile_link=row[4],
            profile_picture=row[5],
            created_at=row[6],
            updated_at=row[7],
            deleted_at=row[8],
        )

    async def get_kick_account_by_username(
        self, username: str
    ) -> Optional[KickAccount]:
        """
        Fetch a user's Kick account details by username.
        """
        normalized_username = username.lower() if username else ""
        row = await self.db.fetch_one(
            SELECT_KICK_ACCOUNT_QUERY + "WHERE kick_username = %s",
            (normalized_username,),
        )

        if not row:
            raise Exception("Kick account not found for user.")

        return KickAccount(
            id=row[0],
            user_id=row[1],
            kick_id=row[2],
            kick_username=row[3],
            profile_link=row[4],
            profile_picture=row[5],
            created_at=row[6],
            updated_at=row[7],
            deleted_at=row[8],
        )

    async def create_twitch_account(
        self, account: TwitchAccountCreate
    ) -> TwitchAccount:
        """
        Insert a new Twitch account for the user.
        """
        res = await self.db.insert(
            table="twitch_account",
            columns=[
                "user_id",
                "twitch_id",
                "twitch_username",
                "profile_link",
                "profile_picture",
                "authorized_at",
                "oauth_token_id",
            ],
            values=(
                account.user_id,
                account.twitch_id,
                account.twitch_username.lower(),
                account.profile_link,
                account.profile_picture,
                account.authorized_at,
                account.oauth_token_id,
            ),
        )
        if not res:
            raise Exception("Failed to create Twitch account for user.")
        return TwitchAccount(
            id=res,
            user_id=account.user_id,
            twitch_id=account.twitch_id,
            twitch_username=account.twitch_username.lower(),
            profile_link=account.profile_link,
            profile_picture=account.profile_picture,
            authorized_at=account.authorized_at,
            oauth_token_id=account.oauth_token_id,
            created_at=datetime.datetime.now(),
            updated_at=datetime.datetime.now(),
        )

    async def create_kick_account(self, account: KickAccountCreate) -> KickAccount:
        """
        Insert a new Kick account for the user.
        """
        res = await self.db.insert(
            table="kick_account",
            columns=[
                "user_id",
                "kick_id",
                "kick_username",
                "profile_link",
                "profile_picture",
            ],
            values=(
                account.user_id,
                account.kick_id,
                account.kick_username.lower(),
                account.profile_link,
                account.profile_picture,
            ),
        )
        if not res:
            raise Exception("Failed to create Kick account for user.")
        return KickAccount(
            id=res,
            user_id=account.user_id,
            kick_id=account.kick_id,
            kick_username=account.kick_username.lower(),
            profile_link=account.profile_link,
            profile_picture=account.profile_picture,
            created_at=datetime.datetime.now(),
            updated_at=datetime.datetime.now(),
        )

    async def upsert_twitch_account(
        self, account: TwitchAccountCreate
    ) -> TwitchAccount:
        """
        Insert a new Twitch account for the user or update if it already exists.
        """
        normalized_twitch_username = account.twitch_username.lower()
        res = await self._upsert_owned_account(
            table="twitch_account",
            columns=[
                "user_id",
                "twitch_id",
                "twitch_username",
                "profile_link",
                "profile_picture",
                "authorized_at",
                "oauth_token_id",
            ],
            values=(
                account.user_id,
                account.twitch_id,
                normalized_twitch_username,
                account.profile_link,
                account.profile_picture,
                account.authorized_at,
                account.oauth_token_id,
            ),
            conflict_column="twitch_id",
            update_columns=[
                "twitch_username",
                "profile_link",
                "profile_picture",
                "authorized_at",
                "oauth_token_id",
            ],
        )
        if not res:
            raise Exception("Failed to create or update Twitch account for user.")
        return TwitchAccount(
            id=res,
            user_id=account.user_id,
            twitch_id=account.twitch_id,
            twitch_username=normalized_twitch_username,
            profile_link=account.profile_link,
            profile_picture=account.profile_picture,
            authorized_at=account.authorized_at,
            oauth_token_id=account.oauth_token_id,
            created_at=datetime.datetime.now(),
            updated_at=datetime.datetime.now(),
        )

    async def upsert_social_media_account(
        self, account: SocialMediaAccountCreate
    ) -> SocialMediaAccount:
        """
        Insert a new Social Media account for the user or update if it already exists.
        """

        res = await self.db.upsert(
            table="social_media_account",
            columns=["user_id", "platform", "oauth_token_id", "username"],
            values=(
                account.user_id,
                account.platform.value,
                account.oauth_token_id,
                account.username,
            ),
            conflict_columns=["user_id", "platform"],
            update_columns=["oauth_token_id", "username"],
        )
        if not res:
            raise Exception("Failed to create or update Social Media account for user.")
        return SocialMediaAccount(
            id=res,
            user_id=account.user_id,
            platform=account.platform,
            created_at=datetime.datetime.now(),
            updated_at=datetime.datetime.now(),
            username=account.username,
        )

    async def upsert_kick_account(self, account: KickAccountCreate) -> KickAccount:
        """
        Insert a new Kick account for the user or update if it already exists.
        """
        normalized_kick_username = account.kick_username.lower()
        res = await self._upsert_owned_account(
            table="kick_account",
            columns=[
                "user_id",
                "kick_id",
                "kick_username",
                "profile_link",
                "profile_picture",
            ],
            values=(
                account.user_id,
                account.kick_id,
                normalized_kick_username,
                account.profile_link,
                account.profile_picture,
            ),
            conflict_column="kick_id",
            update_columns=[
                "profile_link",
                "profile_picture",
                "kick_username",
            ],
        )
        if not res:
            raise Exception("Failed to create or update Kick account for user.")
        return KickAccount(
            id=res,
            user_id=account.user_id,
            kick_id=account.kick_id,
            kick_username=normalized_kick_username,
            profile_link=account.profile_link,
            profile_picture=account.profile_picture,
            created_at=datetime.datetime.now(),
            updated_at=datetime.datetime.now(),
        )

    async def update_twitch_account(
        self, user_id: int, account: TwitchAccountUpdate
    ) -> None:
        """
        Update (overwrite) an existing Twitch account row.
        """
        account.twitch_username = (
            account.twitch_username.lower() if account.twitch_username else None
        )
        update_dict = account.dict(exclude_none=True)
        if not update_dict:
            return

        set_clauses = []
        values = []

        for key, value in update_dict.items():
            set_clauses.append(key)
            values.append(value)

        await self.db.update(
            table="twitch_account",
            set_columns=set_clauses,
            set_values=values,
            where_clause="user_id = %s",
            where_params=(user_id,),
        )

    async def update_kick_account(
        self, user_id: int, account: KickAccountUpdate
    ) -> None:
        """
        Update (overwrite) an existing Kick account row.
        """
        account.kick_username = (
            account.kick_username.lower() if account.kick_username else None
        )
        update_dict = account.dict(exclude_none=True)
        if not update_dict:
            return

        set_clauses = []
        values = []

        for key, value in update_dict.items():
            set_clauses.append(key)
            values.append(value)

        await self.db.update(
            table="kick_account",
            set_columns=set_clauses,
            set_values=values,
            where_clause="user_id = %s",
            where_params=(user_id,),
        )

    async def delete_twitch_account(self, user_id: int) -> None:
        """
        Delete a user's Twitch account.
        """
        await self.db.delete(
            table="twitch_account",
            where_clause="user_id = %s",
            where_params=(user_id,),
        )

    async def delete_kick_account(self, user_id: int) -> None:
        """
        Delete a user's Kick account.
        """
        await self.db.delete(
            table="kick_account",
            where_clause="user_id = %s",
            where_params=(user_id,),
        )

    async def list_all_twitch_accounts(self) -> list[tuple[int, str]]:
        """List all active Twitch accounts (user_id, twitch_id).

        Returns:
            List of tuples: (user_id, twitch_id)
        """
        rows = await self.db.fetch_all(
            "SELECT user_id, twitch_id FROM twitch_account WHERE deleted_at IS NULL",
            (),
        )
        return [(row[0], str(row[1])) for row in (rows or [])]

    async def list_twitch_accounts_with_last_login(
        self,
    ) -> list[tuple[int, str, datetime.datetime, str]]:
        """
        List all active Twitch accounts with last_login and tier metadata.

        Returns:
            List of tuples: (user_id, twitch_id, last_login, tier)
        """
        rows = await self.db.fetch_all(
            """
            SELECT ta.user_id, ta.twitch_id, u.last_login, u.tier
            FROM twitch_account ta
            JOIN users u ON u.id = ta.user_id
            WHERE ta.deleted_at IS NULL AND u.deleted_at IS NULL
            """,
            (),
        )
        return [(row[0], str(row[1]), row[2], row[3]) for row in (rows or [])]

    async def list_all_kick_accounts(self) -> list[tuple[int, str]]:
        """List all active Kick accounts (user_id, kick_id).

        Returns:
            List of tuples: (user_id, kick_id)
        """
        rows = await self.db.fetch_all(
            "SELECT user_id, kick_id FROM kick_account WHERE deleted_at IS NULL",
            (),
        )
        return [(row[0], str(row[1])) for row in (rows or [])]

    async def list_kick_accounts_with_last_login(
        self,
    ) -> list[tuple[int, str, datetime.datetime, str]]:
        """
        List all active Kick accounts with last_login and tier metadata.

        Returns:
            List of tuples: (user_id, kick_id, last_login, tier)
        """
        rows = await self.db.fetch_all(
            """
            SELECT ka.user_id, ka.kick_id, u.last_login, u.tier
            FROM kick_account ka
            JOIN users u ON u.id = ka.user_id
            WHERE ka.deleted_at IS NULL AND u.deleted_at IS NULL
            """,
            (),
        )
        return [(row[0], str(row[1]), row[2], row[3]) for row in (rows or [])]

    async def get_user_oauth_token(
        self, user_id: int, platform: Platform
    ) -> UserAccessToken:
        """
        Fetch a user's OAuth token for the given platform.
        """
        row = await self.db.fetch_one(
            SELECT_OAUTH_TOKEN_QUERY + "WHERE user_id = %s AND platform = %s",
            (user_id, platform.value),
        )

        if not row:
            raise Exception(
                f"{platform.value.capitalize()} OAuth token not found for user."
            )

        return self._row_to_oauth_token(row)

    async def get_valid_twitch_oauth_token(
        self, user_id: int, twitch_api, force_refresh: bool = False
    ) -> UserAccessToken:
        """Return a valid Twitch OAuth token for a user.

        When ``force_refresh`` is True, refreshes even if token isn't time-expired.
        """
        try:
            user_token = await self.get_user_oauth_token(user_id, Platform.TWITCH)
        except Exception as exc:
            raise Exception(
                f"Twitch OAuth token not found for user {user_id}: {exc}"
            ) from exc

        try:
            if force_refresh:
                return await self.refresh_twitch_token(user_id, user_token, twitch_api)
            return await self.refresh_twitch_token_if_needed(
                user_id, user_token, twitch_api
            )
        except Exception as exc:
            raise Exception(
                f"Failed to obtain valid Twitch OAuth token for user {user_id}: {exc}"
            ) from exc

    async def create_user_oauth_token(
        self, token: UserAccessTokenCreate
    ) -> UserAccessToken:
        """
        Insert a new OAuth token (one row per user & platform – unique enforced).
        """
        enc_access = self.crypto.encrypt(token.access_token.encode())
        enc_refresh = (
            self.crypto.encrypt(token.refresh_token.encode())
            if token.refresh_token
            else None
        )

        res = await self.db.insert(
            table="user_oauth_tokens",
            columns=[
                "user_id",
                "platform",
                "access_token",
                "refresh_token",
                "token_type",
                "expires_in",
                "scope",
            ],
            values=(
                token.user_id,
                token.platform.value,
                enc_access,
                enc_refresh,
                token.token_type,
                token.expires_in,
                ",".join(token.scope or []),
            ),
        )
        if not res:
            raise Exception(
                f"Failed to create {token.platform.value} OAuth token for user."
            )

        return UserAccessToken(
            id=res,
            user_id=token.user_id,
            platform=token.platform,
            access_token=token.access_token,
            refresh_token=token.refresh_token,
            token_type=token.token_type,
            expires_in=token.expires_in,
            scope=token.scope,
            created_at=datetime.datetime.now(),
            updated_at=datetime.datetime.now(),
        )

    async def upsert_user_oauth_token(
        self, token: UserAccessTokenCreate
    ) -> UserAccessToken:
        """
        Insert a new OAuth token (one row per user & platform – unique enforced).
        """
        enc_access = self.crypto.encrypt(token.access_token.encode())
        enc_refresh = (
            self.crypto.encrypt(token.refresh_token.encode())
            if token.refresh_token
            else None
        )

        res = await self.db.upsert(
            table="user_oauth_tokens",
            columns=[
                "user_id",
                "platform",
                "access_token",
                "refresh_token",
                "token_type",
                "expires_in",
                "scope",
                "updated_at",
            ],
            values=(
                token.user_id,
                token.platform.value,
                enc_access,
                enc_refresh,
                token.token_type,
                token.expires_in,
                ",".join(token.scope or []),
                datetime.datetime.utcnow(),
            ),
            conflict_columns=["user_id", "platform"],
            update_columns=[
                "access_token",
                "refresh_token",
                "token_type",
                "expires_in",
                "scope",
                "updated_at",
            ],
        )
        if not res:
            raise Exception(
                f"Failed to create {token.platform.value} OAuth token for user."
            )

        return UserAccessToken(
            id=res,
            user_id=token.user_id,
            platform=token.platform,
            access_token=token.access_token,
            refresh_token=token.refresh_token,
            token_type=token.token_type,
            expires_in=token.expires_in,
            scope=token.scope,
            created_at=datetime.datetime.now(),
            updated_at=datetime.datetime.now(),
        )

    async def update_user_oauth_token(
        self, user_id: int, token: UserAccessTokenUpdate
    ) -> None:
        """
        Update (overwrite) an existing OAuth token row.
        """
        if token.access_token is None:
            raise ValueError("OAuth token update requires access_token")
        if token.platform is None:
            raise ValueError("OAuth token update requires platform")

        enc_access = self.crypto.encrypt(token.access_token.encode())
        enc_refresh = (
            self.crypto.encrypt(token.refresh_token.encode())
            if token.refresh_token
            else None
        )

        await self.db.update(
            table="user_oauth_tokens",
            set_columns=[
                "access_token",
                "refresh_token",
                "token_type",
                "expires_in",
                "scope",
                "updated_at",
            ],
            set_values=[
                enc_access,
                enc_refresh,
                token.token_type,
                token.expires_in,
                ",".join(token.scope or []),
                "NOW()",
            ],
            where_clause="user_id = %s AND platform = %s",
            where_params=(user_id, token.platform.value),
        )

    async def delete_user_oauth_token(self, user_id: int, platform: Platform) -> None:
        """
        Delete a user's OAuth token for the given platform.
        """
        await self.db.delete(
            table="user_oauth_tokens",
            where_clause="user_id = %s AND platform = %s",
            where_params=(user_id, platform.value),
        )

    async def refresh_kick_token_if_needed(
        self, user_id: int, user_token: UserAccessToken, kick_api
    ) -> UserAccessToken:
        """
        Check if Kick token is expired and refresh if needed.

        Args:
            user_id: The user ID
            user_token: Current user access token
            kick_api: KickApi instance for refreshing tokens

        Returns:
            UserAccessToken: The current token if valid, or refreshed token

        Raises:
            ValueError: If no refresh token is available
            Exception: If token refresh fails
        """
        # Check if token is expired
        if not user_token.is_expired():
            return user_token

        if not user_token.refresh_token:
            raise ValueError(f"No refresh token available for user {user_id}")

        logger.info(
            "Kick token expired, refreshing",
            extra={"user_id": user_id, "platform": "kick"},
        )

        # Refresh the token
        token_data = await kick_api.refresh_user_token(user_token.refresh_token)

        # Update in database
        token_update = UserAccessTokenUpdate(
            access_token=token_data["access_token"],
            refresh_token=token_data.get("refresh_token"),
            expires_in=token_data["expires_in"],
            token_type=token_data["token_type"],
            scope=(
                token_data.get("scope", "").split(" ")
                if isinstance(token_data.get("scope"), str)
                else token_data.get("scope")
            )
            if token_data.get("scope")
            else None,
            platform=Platform.KICK,
        )
        await self.update_user_oauth_token(user_id, token_update)

        logger.info(
            "Successfully refreshed Kick token",
            extra={"user_id": user_id, "platform": "kick"},
        )

        # Return refreshed token from database
        return await self.get_user_oauth_token(user_id, Platform.KICK)

    async def refresh_twitch_token_if_needed(
        self, user_id: int, user_token: UserAccessToken, twitch_api
    ) -> UserAccessToken:
        """
        Check if Twitch token is expired and refresh if needed.

        Args:
            user_id: The user ID
            user_token: Current user access token
            twitch_api: TwitchApi instance for refreshing tokens

        Returns:
            UserAccessToken: The current token if valid, or refreshed token

        Raises:
            ValueError: If no refresh token is available
            Exception: If token refresh fails
        """
        # Check if token is expired
        if not user_token.is_expired():
            return user_token

        return await self.refresh_twitch_token(user_id, user_token, twitch_api)

    async def refresh_twitch_token(
        self, user_id: int, user_token: UserAccessToken, twitch_api
    ) -> UserAccessToken:
        """Refresh a Twitch user token regardless of current expiry state."""
        if not user_token.refresh_token:
            raise ValueError(f"No refresh token available for user {user_id}")

        logger.info(
            "Refreshing Twitch token",
            extra={"user_id": user_id, "platform": "twitch"},
        )

        # Refresh the token
        token_data = await twitch_api.refresh_user_token(user_token.refresh_token)

        # Update in database
        token_update = UserAccessTokenUpdate(
            access_token=token_data["access_token"],
            refresh_token=token_data.get("refresh_token"),
            expires_in=token_data["expires_in"],
            token_type=token_data["token_type"],
            scope=(
                token_data.get("scope", "").split(" ")
                if isinstance(token_data.get("scope"), str)
                else token_data.get("scope")
            )
            if token_data.get("scope")
            else None,
            platform=Platform.TWITCH,
        )
        await self.update_user_oauth_token(user_id, token_update)

        logger.info(
            "Successfully refreshed Twitch token",
            extra={"user_id": user_id, "platform": "twitch"},
        )

        # Return refreshed token from database
        return await self.get_user_oauth_token(user_id, Platform.TWITCH)

    async def get_name_by_user_id(self, user_id: int) -> str:
        """Return a display name for the user.

        Priority:
        1) Twitch username
        2) Kick username
        3) First name + last name (fallback)
        """
        query = """
            SELECT
                ta.twitch_username,
                ka.kick_username,
                u.first_name,
                u.last_name
            FROM users u
            LEFT JOIN twitch_account ta ON u.id = ta.user_id AND ta.deleted_at IS NULL
            LEFT JOIN kick_account   ka ON u.id = ka.user_id AND ka.deleted_at IS NULL
            WHERE u.id = %s AND u.deleted_at IS NULL
            LIMIT 1
        """

        row = await self.db.fetch_one(query, (user_id,))
        if not row:
            raise UserNotFoundError(f"User with ID {user_id} not found.")

        twitch_username, kick_username, first_name, last_name = row

        if isinstance(twitch_username, str) and twitch_username.strip():
            return twitch_username.strip()

        if isinstance(kick_username, str) and kick_username.strip():
            return kick_username.strip()

        fn = first_name.strip() if isinstance(first_name, str) else ""
        ln = last_name.strip() if isinstance(last_name, str) else ""
        full = f"{fn} {ln}".strip()

        return full if full else ""

    @staticmethod
    def _row_to_twitch_account(row) -> TwitchAccount | None:
        """Convert a database row to a TwitchAccount model."""
        if not row:
            return None
        if len(row) < 9:
            raise ValueError(
                "Row does not contain enough data to create a TwitchAccount object."
            )
        account_data = row[:9]
        return TwitchAccount(
            id=account_data[0],
            user_id=account_data[1],
            twitch_id=account_data[2],
            twitch_username=account_data[3],
            profile_link=account_data[4],
            profile_picture=account_data[5],
            created_at=account_data[6],
            updated_at=account_data[7],
            deleted_at=account_data[8],
        )

    @staticmethod
    def _row_to_kick_account(row) -> KickAccount | None:
        """Convert a database row to a KickAccount model."""
        if not row:
            return None
        if len(row) < 9:
            raise ValueError(
                "Row does not contain enough data to create a KickAccount object."
            )
        account_data = row[:9]
        return KickAccount(
            id=account_data[0],
            user_id=account_data[1],
            kick_id=account_data[2],
            kick_username=account_data[3],
            profile_link=account_data[4],
            profile_picture=account_data[5],
            created_at=account_data[6],
            updated_at=account_data[7],
            deleted_at=account_data[8],
        )

    def _row_to_oauth_token(self, row: tuple[Any, ...]) -> UserAccessToken:
        """Convert a database row to an AccessToken model."""
        if not row:
            raise ValueError("Row is empty; cannot create an AccessToken object.")
        if len(row) < 11:
            raise ValueError(
                "Row does not contain enough data to create an AccessToken object."
            )
        token_data = row[:11]
        platform_raw = token_data[2]
        if isinstance(platform_raw, Platform):
            platform = platform_raw
        elif isinstance(platform_raw, str):
            platform = Platform(platform_raw.lower())
        else:
            raise ValueError("OAuth token platform is missing or invalid.")

        access_token = self._decrypt_token_value(token_data[3])
        if access_token is None:
            raise ValueError("OAuth token access_token is missing.")

        token_type_raw = token_data[5]
        token_type = str(token_type_raw) if token_type_raw is not None else ""
        expires_in_raw = token_data[6]
        expires_in = int(expires_in_raw) if expires_in_raw is not None else 0
        scope_raw = token_data[7]
        scope = (
            scope_raw.split(",") if isinstance(scope_raw, str) and scope_raw else None
        )
        return UserAccessToken(
            id=token_data[0],
            user_id=token_data[1],
            platform=platform,
            access_token=access_token,
            refresh_token=self._decrypt_token_value(token_data[4]),
            token_type=token_type,
            expires_in=expires_in,
            scope=scope,
            created_at=token_data[8],
            updated_at=token_data[9],
            deleted_at=token_data[10],
        )

    def _row_to_user(self, row: tuple[Any, ...]) -> User:
        """Convert a database row to a User model."""
        if not row:
            raise ValueError("Row is empty; cannot create a User object.")
        if len(row) < USER_COLUMNS_LEN:
            raise ValueError(
                "Row does not contain enough data to create a User object."
            )

        # Extract data from the row based on the new query structure
        user_data = row[:USER_COLUMNS_LEN]
        twitch_account_data = row[
            USER_COLUMNS_LEN : USER_COLUMNS_LEN + TWITCH_ACCOUNT_COLUMNS_LEN
        ]
        kick_account_data = row[
            USER_COLUMNS_LEN + TWITCH_ACCOUNT_COLUMNS_LEN : USER_COLUMNS_LEN
            + TWITCH_ACCOUNT_COLUMNS_LEN
            + KICK_ACCOUNT_COLUMNS_LEN
        ]
        twitch_oauth_data = row[
            USER_COLUMNS_LEN
            + TWITCH_ACCOUNT_COLUMNS_LEN
            + KICK_ACCOUNT_COLUMNS_LEN : USER_COLUMNS_LEN
            + TWITCH_ACCOUNT_COLUMNS_LEN
            + KICK_ACCOUNT_COLUMNS_LEN
            + TWITCH_ACCOUNT_OAUTH_COLUMNS_LEN
        ]
        kick_oauth_data = row[
            USER_COLUMNS_LEN
            + TWITCH_ACCOUNT_COLUMNS_LEN
            + KICK_ACCOUNT_COLUMNS_LEN
            + TWITCH_ACCOUNT_OAUTH_COLUMNS_LEN : USER_COLUMNS_LEN
            + TWITCH_ACCOUNT_COLUMNS_LEN
            + KICK_ACCOUNT_COLUMNS_LEN
            + TWITCH_ACCOUNT_OAUTH_COLUMNS_LEN
            + KICK_ACCOUNT_OAUTH_COLUMNS_LEN
        ]

        social_media_platforms = row[
            USER_COLUMNS_LEN
            + TWITCH_ACCOUNT_COLUMNS_LEN
            + KICK_ACCOUNT_COLUMNS_LEN
            + TWITCH_ACCOUNT_OAUTH_COLUMNS_LEN
            + KICK_ACCOUNT_OAUTH_COLUMNS_LEN
        ]

        # Create the user object with basic user data
        user = User(
            id=user_data[0],
            first_promoter_id=user_data[1],
            first_name=user_data[2],
            last_name=user_data[3],
            email=user_data[4],
            phone=user_data[5],
            tier=user_data[6],
            product=user_data[7],
            created_at=user_data[8],
            updated_at=user_data[9],
            deleted_at=user_data[10],
            last_login=user_data[11],
            customer_journey_completed_at=user_data[12],
            social_media_platforms=social_media_platforms,
        )

        # Handle Twitch account data
        # Check if all required fields for TwitchAccount are not None
        if (
            not any(twitch_account_data)
            or twitch_account_data[0] is None  # id
            or twitch_account_data[1] is None  # twitch_id
            or twitch_account_data[2] is None  # twitch_username
            or twitch_account_data[5] is None  # created_at
            or twitch_account_data[6] is None
        ):  # updated_at
            user.twitch_account = None
        else:
            user.twitch_account = TwitchAccount(
                id=twitch_account_data[0],
                user_id=user.id,
                twitch_id=twitch_account_data[1],
                twitch_username=twitch_account_data[2],
                profile_link=twitch_account_data[3],
                profile_picture=twitch_account_data[4],
                created_at=twitch_account_data[5],
                updated_at=twitch_account_data[6],
                deleted_at=twitch_account_data[7],
            )

            # Add Twitch OAuth token if available
            if any(twitch_oauth_data):
                twitch_access_token = self._decrypt_token_value(twitch_oauth_data[2])
                if twitch_access_token is None:
                    raise ValueError("Missing Twitch access token.")
                user.twitch_account.access_token = UserAccessToken(
                    id=twitch_oauth_data[0],
                    user_id=user.id,
                    platform=Platform.TWITCH,
                    access_token=twitch_access_token,
                    refresh_token=self._decrypt_token_value(twitch_oauth_data[3]),
                    token_type=twitch_oauth_data[4],
                    expires_in=twitch_oauth_data[5],
                    scope=(
                        twitch_oauth_data[6].split(",")
                        if isinstance(twitch_oauth_data[6], str)
                        else twitch_oauth_data[6]
                    )
                    if twitch_oauth_data[6]
                    else None,
                    created_at=twitch_oauth_data[7],
                    updated_at=twitch_oauth_data[8],
                    deleted_at=twitch_oauth_data[9],
                )
                logger.info(
                    f"Twitch OAuth token successfully decrypted for user {user.id}"
                )

        # Handle Kick account data
        # Check if all required fields for KickAccount are not None
        if (
            not any(kick_account_data)
            or kick_account_data[0] is None  # id
            or kick_account_data[1] is None  # kick_id
            or kick_account_data[2] is None  # kick_username
            or kick_account_data[5] is None  # created_at
            or kick_account_data[6] is None
        ):  # updated_at
            user.kick_account = None
        else:
            user.kick_account = KickAccount(
                id=kick_account_data[0],
                user_id=user.id,
                kick_id=kick_account_data[1],
                kick_username=kick_account_data[2],
                profile_link=kick_account_data[3],
                profile_picture=kick_account_data[4],
                created_at=kick_account_data[5],
                updated_at=kick_account_data[6],
                deleted_at=kick_account_data[7],
            )

            # Add Kick OAuth token if available
            if any(kick_oauth_data):
                kick_access_token = self._decrypt_token_value(kick_oauth_data[2])
                if kick_access_token is None:
                    raise ValueError("Missing Kick access token.")
                user.kick_account.access_token = UserAccessToken(
                    id=kick_oauth_data[0],
                    user_id=user.id,
                    platform=Platform.KICK,
                    access_token=kick_access_token,
                    refresh_token=self._decrypt_token_value(kick_oauth_data[3]),
                    token_type=kick_oauth_data[4],
                    expires_in=kick_oauth_data[5],
                    scope=(
                        kick_oauth_data[6].split(",")
                        if isinstance(kick_oauth_data[6], str)
                        else kick_oauth_data[6]
                    )
                    if kick_oauth_data[6]
                    else None,
                    created_at=kick_oauth_data[7],
                    updated_at=kick_oauth_data[8],
                    deleted_at=kick_oauth_data[9],
                )

        return user

    def _decrypt_token_value(self, token: Any) -> str | None:
        if token is None:
            return None
        token_bytes: bytes
        if isinstance(token, bytes):
            token_bytes = token
        elif isinstance(token, memoryview):
            token_bytes = token.tobytes()
        elif isinstance(token, str):
            token_bytes = token.encode()
        else:
            raise TypeError(f"Unsupported token payload type: {type(token)!r}")
        return self.crypto.decrypt(token_bytes).decode()

    def decrypt_token(self, token: Any) -> str:
        decrypted = self._decrypt_token_value(token)
        if decrypted is None:
            raise ValueError("Token payload cannot be None")
        return decrypted

    def encrypt_token(self, token: str) -> bytes:
        return self.crypto.encrypt(token.encode())
