"""

    AUTHOR:

        Khushal P Soonderji

    DATE:

        Thursday, 12th Dec., 2024

    OBJECTIVE:

        To handle all auth-tokens from one place.

    REFERENCES:

        N/A

    DOWNLOADS:

        N/A

"""

#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                   IMPORT                                                      ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# To make sibling directories accessible for imports:
import sys
sys.path.append(".")
sys.path.append("..")

# My async utils:
from utils_v2.string import json
from utils_v2.date_time import date_time
from utils_v2.database.async_mysql_v2 import AsyncMySQL
from utils_v2.database.async_mongo_v2 import AsyncMongo

# Base model:
from controllers.base import BaseModel

# Data models:
from models.core.auth_token import CoreAuthTokenModel

# To work with datatypes:
from typing import List

# To work with MongoDB:
from bson import ObjectId


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                           MACROS / ONE-TIME INIT                                              ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# --- Nothing Yet


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                VARIABLES                                                      ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# --- Nothing Yet


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                  FUNCTIONS                                                    ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


# --- Nothing Yet


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                                   CLASSES                                                     ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


class CoreAuthTokenController(BaseModel):

    # ┏┓┓      ┓┏
    # ┃ ┃┏┓┏┏  ┃┃┏┓┏┓┏
    # ┗┛┗┗┻┛┛  ┗┛┗┻┛ ┛

    # For MongoDB:
    AUTH_COLLECTION = "_authTokens"

    async def get_token_key(
            self,
            db_conn: AsyncMySQL,
            mongo_conn: AsyncMongo,
            auth_token: CoreAuthTokenModel,
            token_notes: dict,
            session_token: str = None,
    ) -> ObjectId:

        """
        Stores params from the session info and gives an identifier to use in the authorization URL. Use this when the
        user requests an authorization URL to link your service to another service (like GMail).
        :param db_conn: The database connection (MariaDB) to use to perform the action.
        :param mongo_conn: The database connection (MongoDB) to use to perform the action.
        :param auth_token: An instance of the core auth-token model that holds data in the database.
        :param token_notes: Any notes to feed into MariaDB with the token identifier.
        :param session_token: The session token of the user who requested this service.
        :return: An ObjectId to later store the granted tokens.
        """

        # Note down the timestamp at which this event occurred:
        request_ts = date_time.get_current_utc_date_time(as_string = False)

        # Get the identifier from the database if it already exists, else create one.
        # BE CAREFUL WITH THE KEYS HERE, THEY SHOULD MATCH THE FIELDS OF THE CORE AUTH-TOKEN MODEL:
        mongo_json = await mongo_conn.find_one_and_update(
            collection = self.AUTH_COLLECTION,
            filter = mongo_conn.dict_to_dot_notation({
                "serviceType": auth_token.serviceType,
                "user": {
                    "entityId": auth_token.user.entityId,
                    "billingAccountId": auth_token.user.billingAccountId
                },
                "clientUserId": auth_token.clientUserId
            }),
            update = {
                "$set": {
                    "lastRequestTs": auth_token.lastRequestTs,
                    "status": auth_token.status,
                    "syncFreq": auth_token.syncFreq
                },
                "$setOnInsert": {
                    "key": auth_token.key,
                    "serviceType": auth_token.serviceType,
                    "client": auth_token.client,
                    "authType": auth_token.authType,
                    "user": auth_token.user.model_dump(),
                    "clientUserId": auth_token.clientUserId,
                    "auth": auth_token.auth,
                    "token": auth_token.token,
                    "firstRefreshTs": auth_token.firstRefreshTs,
                    "lastRefreshTs": auth_token.lastRefreshTs,
                    "firstRequestTs": auth_token.firstRequestTs or request_ts,
                }
            },
            projection = {
                "_id": True,
                "key": True
            },
            upsert = True,
            return_updated = True
        )

        # Tell MariaDB that an authorization request was initiated:
        db_json = {}
        if mongo_json is not None:
            db_json = await self.call_procedure(
                db_conn = db_conn,
                proc_name = "entity_integration_save",
                proc_args = (
                    auth_token.user.entityId,  # ..................................... 'p_entity_id'
                    auth_token.client,  # ............................................ 'p_provider'
                    auth_token.status,  # ............................................ 'p_current_status'
                    "Auth Requested",  # ............................................. 'p_last_action'
                    None,  # ......................................................... 'p_display_name'
                    None,  # ......................................................... 'p_display_picture'
                    mongo_json["key"],  # ............................................ 'p_token_id'
                    json.to_string(python_data = token_notes, no_space = True),  # ... 'p_notes'
                    auth_token.user.userId  # ........................................ 'p_created_by'
                ),
                session_token = session_token
            )

        # Done here:
        return mongo_json["key"] if mongo_json and db_json.get("status") == 1 else None

    async def set_token(
            self,
            db_conn: AsyncMySQL,
            mongo_conn: AsyncMongo,
            token_key: ObjectId | str,
            auth_token: CoreAuthTokenModel,
            token_notes: dict,
            session_token: str = None
    ) -> bool:

        """
        This method is to be called when the end user authorizes your service to connect to his third-party account. For
        example, when the end user allows you to access his GMail account. USE THIS FOR UPDATING (REFRESHING) TOKENS
        ALSO.
        :param db_conn: The database connection (MariaDB) to use to perform the action.
        :param mongo_conn: The database connection (MongoDB) to use to perform the action.
        :param token_key: The identifier granted by the 'get_token_key' method.
        :param auth_token: The actual auth/token data to be saved to the database.
        :param token_notes: Any notes to feed into MariaDB with the token identifier.
        :param session_token: The session token of the user who requested this service.
        :return: True if saved, False if failed.
        """

        # Start by assuming failure:
        token_saved = False

        # Note down the timestamp at which this event occurred:
        request_ts = date_time.get_current_utc_date_time(as_string = False)

        # Save the token to MongoDB.
        # BE CAREFUL WITH THE KEYS HERE, THEY SHOULD MATCH THE FIELDS OF THE CORE AUTH-TOKEN MODEL:
        mongo_json = await mongo_conn.find_one_and_update(
            collection = self.AUTH_COLLECTION,
            filter = mongo_conn.dict_to_dot_notation({
                "key": ObjectId(token_key),
                "clientUserId": auth_token.clientUserId
            }),
            update = [{
                "$set": {
                    "auth": auth_token.auth,
                    "token": auth_token.token,
                    "status": auth_token.status,
                    "lastRefreshTs": request_ts,
                    "firstRefreshTs": {
                        "$cond": {
                            "if": {
                                "$or": [
                                    {"$eq": ["$firstRefreshTs", None]},
                                    {"$eq": [{"$type": "$firstRefreshTs"}, "missing"]}
                                ]
                            },
                            "then": request_ts,
                            "else": "$firstRefreshTs"
                        }
                    }
                }
            }],
            projection = {"token": False},
            return_updated = True,
            upsert = False
        )

        # Tell MariaDB that the token was saved:
        if mongo_json is not None:
            db_json = await self.call_procedure(
                db_conn = db_conn,
                proc_name = "entity_integration_save",
                proc_args = (
                    mongo_json["user"]["entityId"],  # ............................... 'p_entity_id'
                    mongo_json["client"],  # ......................................... 'p_provider'
                    auth_token.status,  # ............................................ 'p_current_status'
                    "Auth Granted",  # ............................................... 'p_last_action'
                    auth_token.token.get("displayName"),  # .......................... 'p_display_name'
                    auth_token.token.get("displayPictureUrl"),  # .................... 'p_display_picture'
                    token_key,  # .................................................... 'p_token_id'
                    json.to_string(python_data = token_notes, no_space = True),  # ... 'p_notes'
                    auth_token.user.userId  # ........................................ 'p_created_by'
                ),
                session_token = session_token
            )
            if db_json["status"] == 1: token_saved = True

        # Done here:
        return token_saved

    async def get_token(
            self,
            mongo_conn: AsyncMongo,
            token_key: ObjectId | str = None,
    ) -> CoreAuthTokenModel | None:

        """
        To retrieve stored tokens from the database. One token at a time.
        :param mongo_conn: The database connection (MongoDB) to use to perform the action.
        :param token_key: The identifier granted by the 'get_token_key' method.
        :return: The retrieved record that has the token, and information about the service and client if found, else
            None when there is no matching record.
        """

        # If there is some filtering possible, we fetch the token:
        token = await mongo_conn.find_one(
            collection = self.AUTH_COLLECTION,
            filter = {"key": ObjectId(token_key)},
        )

        # Done here:
        return CoreAuthTokenModel(**token) if token else None

    async def get_tokens(
            self,
            mongo_conn: AsyncMongo,
            token_keys: List[ObjectId | str] = None,
    ) -> List[CoreAuthTokenModel]:

        """
        To retrieve stored tokens from the database. Multiple tokens at a time.
        :param mongo_conn: The database connection (MongoDB) to use to perform the action.
        :param token_keys: the identifiers granted by the 'get_token_key' method.
        :return: The retrieved record that has the token, and information about the service and client if found, else
            None when there is no matching record.
        """

        # If there is some filtering possible, we fetch the token:
        tokens = await mongo_conn.find_many(
            collection = self.AUTH_COLLECTION,
            filter = {"key": {"$in": [ObjectId(k) for k in token_keys]}}
        )

        # Done here:
        return [CoreAuthTokenModel(**token) for token in tokens]


#   *****************************************************************************************************************
#  *****                                                                                                          ****
# ***                                               MAIN PROGRAM                                                    ***
#  *****                                                                                                          ****
#   *****************************************************************************************************************


if __name__ == "__main__":

    pass
