"""

    AUTHOR:

        Khushal P Soonderji

    DATE:

        Friday, 17th Jan., 2025.

    OBJECTIVE:

        To receive requests for synchronising mails from various mail clients to the database. Sync'ing means we pull
        the mail from the mail client (like Gmail) and store it to our database. The mail is then ready for showing on
        the UI at any time.

    REFERENCES:

        N/A

    DOWNLOADS:

        N/A

    NOTES:

        N/A

"""


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


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

# For using Quart:
from quart import Blueprint, current_app, g, request

# My utils:
from utils_v2.string import json
from utils_v2.database.async_mongo_v2 import AsyncMongo
from utils_v2.api.codes import StatusCodes, HttpCodes
from utils_v2.api.response import ResponseModel
from utils_v2.api.async_quart import (
    set_api_version,
    read_input,
    get_session_info,
    log_request_to_mongo,
    log_chain_to_mongo,
    should_not_be_under_maintenance,
    only_whitelisted_ips,
    limit_rate,
    validate_input,
    handle_cancelled_request
)

# GMail-related utils:
from utils_v2.goog.controllers.gmail.gmail_client import SCOPES_GMAIL_MAIL_MANAGEMENT
from utils_v2.goog.models.auth_tokens import GoogleAuthTokens

# Common:
from shared import constants

# Data Models:
from models.core.user import CoreUserInfoModel
from models.api.message.mail.sync import MailSyncRequestHeaders, MailSyncRequestData
from models.message.mail.sync import MailSyncOneResult, MailSyncManyResults

# To work with datatypes:
from typing import Literal

# For asynchronous activities:
import asyncio

# To work with LLMs:
from langchain_openai import ChatOpenAI

# To work with date and time:
import datetime


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


# Related to Quart:
mail_sync_bp = Blueprint("mail_sync", __name__)


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


# --- Nothing Yet


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


@mail_sync_bp.record_once
def init(blueprint_setup_state):

    # This gets called when the blueprint is registered.
    # Consider this to be a one-time setup for the whole blueprint:
    pass


# ---------------------------------------------------------------------------------------------------------------------


async def sync_mails(
        user_info: CoreUserInfoModel,
        inbound_headers: dict,
        inbound_data: MailSyncRequestData,
        is_background: bool = True
) -> MailSyncManyResults:

    """
    A very simple function, but kept separate so that we get the option to switch between running it in the foreground
    and running it in the background.
    :param user_info: The information of the user as extracted from the session token.
    :param inbound_headers: The headers that came in with the request.
    :param inbound_data: The data that came in with the request.
    :param is_background: To know whether, or not, this request is being services in the background. If it is happening
        in the foreground, user feedback is not a concern. But, if it is happening in the background, user feedback will
        be a concern. This flag will help alter the treatment of sending alerts.
    :return: The results of the mail-sync'ing attempt.
    """

    # Start by assuming failure:
    client_controller = None
    client_connector = None
    sync_results = MailSyncManyResults()

    # Get the auth-token from the key:
    auth_token = await current_app.mail_controller.get_token_from_key(
        mongo_data_conn = current_app.data_mongo,
        token_key = inbound_data.tokenKey,
        must_be_active = True
    )
    if not auth_token: return constants.API_RESPONSE_NO_AUTH_TOKEN

    # Figure out the client connector:
    match auth_token.client:
        case "gmail": client_controller, client_connector = current_app.gmail_controller, current_app.gmail_client
        case _: client_controller, client_connector = None, None

    # Invoke the client:
    if client_controller is not None and client_connector is not None:
        sync_results = await client_controller.sync_mails(
            sql_conn = current_app.sql_writer,
            mongo_data_conn = current_app.data_mongo,
            mail_client = client_connector,
            auth_token = auth_token,
            user_info = user_info,
            llm = current_app.llm,
            force_sync = inbound_data.forceSync,
            start_date = inbound_data.startDate,
            end_date = inbound_data.endDate,
            max_count = inbound_data.maxCount,
            session_token = inbound_headers["X-Session-Token"]
        )

    # If the client was not matched:
    else: sync_results.message = f"Invalid/unimplemented client '{auth_token.client}'"

    # Send an alert if this is a background process:
    if is_background:
        pass

    # Done here:
    return sync_results


# ---------------------------------------------------------------------------------------------------------------------


@mail_sync_bp.route("/sync", methods = ["POST"])
@mail_sync_bp.route("/sync/<mode>", methods = ["POST"])
@set_api_version(api_version = "1.0.0")
@read_input(sanitize_headers = False, sanitize_data = False)
@get_session_info(key = "X-Session-Token", session_coro = "get_session")
@log_request_to_mongo(
    attr_name = "logs_mongo",
    project = constants.PROJECT_NAME,
    log_type = constants.MODULE_NAME,
    operation = "mailSyncApi",
    log_input = True,
    log_output = True,
    sensitive_keys = ["sessionToken", "X-Session-Token"]
)
@log_chain_to_mongo(attr_name = "logs_mongo")
@should_not_be_under_maintenance(attr_name = "is_under_maintenance")
@validate_input(
    header_validator = lambda x: MailSyncRequestHeaders(**x).model_dump(),
    data_validator = lambda x: MailSyncRequestData(**x)
)
@handle_cancelled_request()
async def sync_mail(
        mode: Literal["background", "bg"] = None,
        inbound_headers: dict | MailSyncRequestHeaders = None,
        inbound_data: dict | MailSyncRequestData = None,
        inbound_files: dict = None,
        **kwargs
):

    """
    Use this when the user wants to pull old mails from some mail client (like GMail) and save it to the database for
    ready access on the UI.
    :param mode: Set it to one of the specified options to make the sync'ing process go to the background.
    :param inbound_headers: auto-extracted by the decorators.
    :param inbound_data: auto-extracted by the decorators.
    :param inbound_files: auto-extracted by the decorators.
    :param kwargs: Any number of extra inputs supplied by the decorators.
    :return: A standard response structure.
    """

    # If the session token is invalid/expired:
    if kwargs.get("session_info") is None:
        return ResponseModel(
            status_code = StatusCodes.FAILED,
            http_code = HttpCodes.UNAUTHORIZED
        )

    # If we've been asked to sync the mails in the background:
    if mode in ["background", "bg"]:
        current_app.add_background_task(
            sync_mails,
            user_info = CoreUserInfoModel(**kwargs["session_info"]),
            inbound_headers = inbound_headers,
            inbound_data = inbound_data,
            is_background = True
        )
        return ResponseModel(
            status_code = StatusCodes.OK,
            http_code = HttpCodes.ACCEPTED,
            message = "Your mails are being sync'd in the background."
        )

    # Otherwise we process it right here:
    sync_results = await sync_mails(
        user_info = CoreUserInfoModel(**kwargs["session_info"]),
        inbound_headers = inbound_headers,
        inbound_data = inbound_data,
        is_background = False
    )

    # Response:
    return ResponseModel(
        status_code = StatusCodes.FAILED if sync_results.failureCount > 0 else StatusCodes.OK,
        http_code = HttpCodes.INTERNAL_SERVER_ERROR if sync_results.failureCount > 0 else HttpCodes.SUCCESS,
        message = sync_results.message,
        data = {
            "totalCount": sync_results.totalCount,
            "attemptedCount": sync_results.attemptedCount,
            "successCount": sync_results.successCount,
            "failureCount": sync_results.failureCount,
            "newCount": sync_results.newCount,
            "existingCount": sync_results.totalCount - sync_results.newCount
        }
    )


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


if __name__ == "__main__":

    pass
