"""

    AUTHOR:

        Khushal P Soonderji

    DATE:

        Thursday, 19th Dec., 2024

    OBJECTIVE:

        To handle all SMS related behaviour for Nimbus It's service from one place.
        This service is for India only.

    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.date_time import date_time
from utils_v2.database.async_mongo_v2 import AsyncMongo
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache

# Controllers:
from controllers_v2.message.sms.base import SMSController

# Models:
from models.core.auth_token import CoreAuthTokenModel
from models.core.message import CoreMessageModel
from models.api.sms.send import (
    NimbusSMSIndiaMessage,
    SMSSendOneResult,
    SMSSendManyResults
)

# SMS Clients:
from utils_v2.sms.india.nimbus.controllers.async_nimbus import AsyncNimbusSMS

# To work with datatypes:
from typing import List, Any

# To make HTTP requests:
import httpx

# For asynchronous activities:
import asyncio


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


# --- Nothing Yet


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


# --- Nothing Yet


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


# --- Nothing Yet


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


class NimbusSMSIndiaController(SMSController):

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

    def __init__(
            self,
            cache: AsyncRedisCache = None,
            http_client: httpx.AsyncClient = None,
            alert_url: str = None,
            debug: bool = True,
            debug_prefix: str = "Nimbus SMS (C) | ",
            debug_only_errors: bool = True
    ):

        """
        This is the foundational controller for all SMS services. This is built on top of the core message controller,
        and, in turn, all individual SMS client controllers must be built on top of this.
        :param cache: The object to use for caching results from database calls.
        :param http_client: The HTTP client
        :param debug: Whether, or not, you would like to print debugging messages:
        :param debug_prefix: The prefix to print with the debugging messages.
        :param debug_only_errors: Whether you would like to print only error messages or all messages.
        :return: None.
        """

        # Invoke the parent's constructor:
        super().__init__(
            cache = cache,
            alert_url = alert_url,
            http_client = http_client,
            base_filter = {"client": "nimbusSmsIndia"},
            debug = debug,
            debug_prefix = debug_prefix,
            debug_only_errors = debug_only_errors
        )

    # ┏┓┳┳┓┏┓  ┏┓     ┓•
    # ┗┓┃┃┃┗┓  ┗┓┏┓┏┓┏┫┓┏┓┏┓
    # ┗┛┛ ┗┗┛  ┗┛┗ ┛┗┗┻┗┛┗┗┫
    #                      ┛

    async def send_one_sms(
            self,
            mongo_data_conn: AsyncMongo,
            auth_token: CoreAuthTokenModel,
            client: AsyncNimbusSMS,
            message: NimbusSMSIndiaMessage,
            tags: List[Any]
    ) -> SMSSendOneResult:

        """
        Use this to send one SMS. There are just 2 steps here - send the SMS, and store its details in the database.
        :param mongo_data_conn: The database connection to use to perform this task.
        :param auth_token: The auth token that will be used to send this message.
        :param client: The third-party SMS client to use to send this message.
        :param message: The actual message that needs to be sent.
        :param tags: Any tags to attach with this SMS for filtering when querying in the listing service.
        :return: The structured result of sending one message.
        """

        # Send the SMS:
        client_response = await client.send_sms(
            recipient_number = message.recipientNo,
            message = message.text,
            template_id = message.templateId
        )

        # Convert the format of the SMS client's response to the core message model.
        sent_message_model = CoreMessageModel(
            ts = client_response.ts,
            syncTs = date_time.get_current_utc_date_time(as_string = False),
            tokenId = auth_token.authTokenId,
            serviceType = auth_token.serviceType,
            client = auth_token.client,
            clientMessageId = client_response.messageId,
            clientThreadId = message.recipientNo,
            isSent = True,
            isBroadcast = False,
            sentSuccessfully = client_response.success,
            sender = auth_token.auth["senderId"],
            recipient = message.recipientNo,
            chat = message.recipientNo,
            message = client_response.model_dump(),
            snippet = message.text,
            aiSnippet = None,
            tags = list(set(tags + ["SMS", "Nimbus SMS", "India"]))
        )

        # Save the result to the database:
        message_id = await self.save_one_message(
            mongo_data_conn = mongo_data_conn,
            message = sent_message_model
        )
        self._printer(message_id, client_response.success)

        # Done here:
        success = True if client_response.success and message_id else False
        return SMSSendOneResult(
            success = success,
            message = "SMS sent successfully." if client_response.success else "SMS sending failed.",
            smsMessage = message
        )

    async def send_many_sms(
            self,
            mongo_data_conn: AsyncMongo,
            auth_token: CoreAuthTokenModel,
            messages: List[NimbusSMSIndiaMessage],
            tags: List[Any]
    ) -> SMSSendManyResults:

        """
        Use this to send multiple SMS messages. This method just calls the individual SMS sending method for every
        individual message, and then aggregates the results.
        :param mongo_data_conn: The database connection to use to perform this task.
        :param auth_token: The auth token that will be used to send this message.
        :param messages: The list of messages to send out.
        :param tags: Any tags to attach with these SMS for filtering when querying in the listing service. The same tags
            will be applied to all messages. Do not call this method if you need to have different tags for all of them.
        :return: The structured result of sending many SMS messages.
        """

        # Start with a blank variable:
        cumulative_results = SMSSendManyResults()

        # Make the client from the auth-token:
        client = AsyncNimbusSMS(
            entity_id = auth_token.auth["entityId"],
            sender_id = auth_token.auth["senderId"],
            user_id = auth_token.auth["userId"],
            api_key = auth_token.auth["apiKey"],
            http_client = self._http_client
        )

        # Create and fire all the SMS-sending tasks:
        tasks = [
            self.send_one_sms(
                mongo_data_conn = mongo_data_conn,
                auth_token = auth_token,
                client = client,
                message = message,
                tags = tags
            )
            for message in messages
        ]
        individual_results = await asyncio.gather(*tasks)

        # Prepare the final result:
        for result in individual_results:
            if result.success: cumulative_results.successCount += 1
            else: cumulative_results.failureCount += 1
            cumulative_results.totalCount += 1
            cumulative_results.smsMessages.append(result.smsMessage)
        cumulative_results.message = f"{cumulative_results.successCount}/{cumulative_results.totalCount} SMS sent."

        # Done here:
        return cumulative_results


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


if __name__ == "__main__":

    pass
