"""

    AUTHOR:

        Khushal P Soonderji

    DATE:

        Thursday, 19th Dec., 2024

    OBJECTIVE:

        To handle all SMS related behaviour 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.database.async_mongo_v2 import AsyncMongo
from utils_v2.cache.async_redis_cache_v2 import AsyncRedisCache

# Controllers:
from controllers_v2.core.message import CoreMessageController

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

# SMS clients:
from utils_v2.sms.india.nimbus.controllers.async_nimbus import AsyncNimbusSMS
from utils_v2.sms.kenya.savvy_bulk_sms.controllers.async_savvy_bulk_sms import AsyncSavvyBulkSMS

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

# To make HTTP requests:
import httpx

# to work with MongoDB:
from bson.objectid import ObjectId

# To make abstract classes:
from abc import ABC, abstractmethod


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


# --- Nothing Yet


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


# --- Nothing Yet


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


# --- Nothing Yet


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


class SMSController(CoreMessageController, ABC):

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

    SERVICE_TYPE = "sms"

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

    def __init__(
            self,
            cache: AsyncRedisCache = None,
            http_client: httpx.AsyncClient = None,
            alert_url: str = None,
            base_filter: dict = None,
            debug: bool = True,
            debug_prefix: str = "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 base_filter: The basic filter that will be applied to all fetching/updating queries. WARNING: THE BASE
            FILTER WILL ALWAYS BE APPLIED AUTOMATICALLY. SET THIS UP WISELY.
        :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.
        """

        # Prepare the combined base filter:
        sms_filter = {}
        for k, v in (base_filter or {}).items(): sms_filter[k] = v
        sms_filter["serviceType"] = self.SERVICE_TYPE

        # Invoke the parent's constructor:
        CoreMessageController.__init__(
            self,
            cache = cache,
            alert_url = alert_url,
            http_client = http_client,
            base_filter = sms_filter,
            debug = debug,
            debug_prefix = debug_prefix,
            debug_only_errors = debug_only_errors
        )

        # Init a variable in a parent:
        self._service_type = self.SERVICE_TYPE

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

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

        """
        To send one SMS message through the third-party client.
        :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.
        """

        pass

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

        """
        To send multiple SMS messages through the third-party client.
        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.
        """

        pass

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

    # We cannot modify the SMS messages themselves, but we can set/unset tags on them for internal referencing and
    # filtering. This will help the users organize their inboxes well.

    async def update_sms_tags(
            self,
            mongo_data_conn: AsyncMongo,
            message_id: ObjectId | str,
            unset_tags: List[str] = None,
            set_tags: List[str] = None
    ) -> bool:

        """
        To set and unset tags on an SMS message.
        :param mongo_data_conn: The database connection to use to perform this action.
        :param message_id: The ObjectId of the document in MongoDb that holds the message.
        :param unset_tags: The list of tags to unset (done before setting new tags).
        :param set_tags: The list of tags to set (done after unsetting old tags).
        :return: True if successful, else False.
        """

        # Simply call the core model:
        return await self.update_message_tags(
            mongo_data_conn = mongo_data_conn,
            message_id = message_id,
            unset_tags = unset_tags,
            set_tags = set_tags
        )


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


if __name__ == "__main__":

    pass
