"""

    AUTHOR:

        Khushal P Soonderji

    DATE:

        Friday, 13th 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("..")

# For Quart:
from quart import current_app

# 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, AsyncMongoStorage

# Base model:
from controllers.base import BaseModel

# Data models:
from models.core.user import CoreUserInfoModel
from models.core.auth_token import CoreAuthTokenModel
from models.core.message import CoreMessageModel
from models.api.sms.send import (
    SMSSendRequestData,
    NimbusSMSIndiaMessage,
    SavvyBulkSMSKenyaMessage,
    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
from utils_v2.sms.models.sms_message import SentSMSMessageModel

# To work with MongoDB:
from bson import ObjectId
from pymongo import InsertOne

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

# To make API calls:
import httpx

# For asynchronous activities:
import asyncio


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


# --- Nothing Yet


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


# --- Nothing Yet


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


# --- Nothing Yet


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


class SMSController:

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

    pass

    # ┓┏  ┓
    # ┣┫┏┓┃┏┓┏┓┏┓┏
    # ┛┗┗ ┗┣┛┗ ┛ ┛
    #      ┛

    pass

    # ┏┓   ┓
    # ┣┫┓┏╋┣┓
    # ┛┗┗┻┗┛┗

    @staticmethod
    async def set_token_direct(
            db_conn: AsyncMySQL,
            mongo_conn: AsyncMongo,
            auth_token: CoreAuthTokenModel,
            session_token: str = None
    ) -> bool:

        # Start by assuming failure:
        success = False

        # Get a token id:
        token_key = await current_app.core_auth_token_controller.get_token_key(
           db_conn = db_conn,
           mongo_conn = mongo_conn,
           auth_token = auth_token,
           token_notes = {},
           session_token = session_token
       )

        # Immediately save the details against that token id:
        success = await current_app.core_auth_token_controller.set_token(
            db_conn = db_conn,
            mongo_conn = mongo_conn,
            token_key = token_key,
            auth_token = auth_token,
            token_notes = {},
            session_token = session_token
        )

        # Done here:
        return success

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

        # Simply call the core model:
        return await current_app.core_auth_token_controller.get_token(
            mongo_conn = mongo_conn,
            token_key = token_key
        )

    # ┏┓     ┓
    # ┗┓┏┓┏┓┏┫
    # ┗┛┗ ┛┗┗┻

    @staticmethod
    async def __send_from_nimbus_sms_india(
            http_client: httpx.AsyncClient,
            auth_token: CoreAuthTokenModel,
            messages: List[NimbusSMSIndiaMessage],
    ) -> SMSSendManyResults:

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

        # Initialize the third-party client:
        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 = http_client
        )

        # Iterate over all the messages you need to send:
        for message in messages:

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

            # Note down the results:
            send_results.totalCount += 1
            if client_response.success: send_results.successCount += 1
            else: send_results.failureCount += 1
            send_results.smsMessages.append(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 = None,
                recipient = message.recipientNo,
                chat = None,
                message = client_response.model_dump(),
                snippet = message.text,
                aiSnippet = None,
                tags = ["sms", "nimbusSmsIndia"]
            ))

        # Done here:
        return send_results

    @staticmethod
    async def __send_from_savvy_bulk_sms_kenya(
            http_client: httpx.AsyncClient,
            auth_token: CoreAuthTokenModel,
            messages: List[SavvyBulkSMSKenyaMessage],
    ) -> SMSSendManyResults:

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

        # Initialize the third-party client:
        client = AsyncSavvyBulkSMS(
            partner_id = auth_token.auth["partnerId"],
            short_code = auth_token.auth["shortCode"],
            api_key = auth_token.auth["apiKey"],
            http_client = http_client
        )

        # Iterate over all the messages you need to send:
        for message in messages:

            # Send the SMS and return the response:
            client_response = await client.send_sms(
                recipient_number = message.recipientNo,
                message = message.text
            )

            # Note down the results:
            send_results.totalCount += 1
            if client_response.success: send_results.successCount += 1
            else: send_results.failureCount += 1
            send_results.smsMessages.append(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 = None,
                recipient = message.recipientNo,
                chat = None,
                message = client_response.model_dump(),
                snippet = message.text,
                aiSnippet = None,
                tags = ["sms", "savvyBulkSmsKenya"]
            ))

        # Done here:
        return send_results

    async def send(
            self,
            mongo_conn: AsyncMongo,
            http_client: httpx.AsyncClient,
            auth_token: CoreAuthTokenModel,
            messages: List[NimbusSMSIndiaMessage | SavvyBulkSMSKenyaMessage]
    ) -> SMSSendManyResults:

        # Start by assuming failure:
        send_results = SMSSendManyResults()

        # Now we route the message to the appropriate client:
        match auth_token.client:
            case "nimbusSmsIndia":
                send_results = await self.__send_from_nimbus_sms_india(
                    http_client = http_client,
                    auth_token = auth_token,
                    messages = messages
                )
            case "savvyBulkSmsKenya":
                send_results = await self.__send_from_savvy_bulk_sms_kenya(
                    http_client = http_client,
                    auth_token = auth_token,
                    messages = messages
                )
            case _:
                send_results.message = f"invalid client {auth_token.client}"

        # Save the results to MongoDB:
        tasks = []
        for sms in send_results.smsMessages:
            message_json = sms.model_dump()
            message_json.pop("_id", None)
            tasks.append(current_app.core_message_controller.insert(
                mongo_conn = mongo_conn,
                message = message_json
            ))
        results = await asyncio.gather(*tasks)

        # Done here:
        send_results.message = f"{send_results.successCount}/{send_results.totalCount} message(s) sent"
        return send_results

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

    # These are simply for retrieving sms messages.
    # You need to already have them saved to the database.

    # @staticmethod
    # async def list_messages(
    #         mongo_conn: AsyncMongo,
    #         token_ids: List[ObjectId | str],
    #         limit: int = 100,
    #         skip: int = 0,
    #         additional_filter: dict = None
    # ) -> List[CoreMessageModel] | None:
    #
    #     # regardless of what additional filter is provided from outside,
    #     # we add a mail-selecting filter here:
    #     if additional_filter is None: additional_filter = {}
    #     additional_filter["serviceType"] = "sms"
    #
    #     # Simply call the core model:
    #     return await current_app.core_message_controller.get_message(
    #         mongo_conn = mongo_conn,
    #         token_ids = token_ids,
    #         limit = limit,
    #         skip = skip,
    #         additional_filter = additional_filter
    #     )
    #
    # @staticmethod
    # async def get_one_mail(
    #         mongo_conn: AsyncMongo,
    #         token_id: ObjectId | str,
    #         message_id: ObjectId | str
    # ) -> CoreMessageModel | None:
    #
    #     # Simply call the core model:
    #     return await current_app.core_message_controller.get_message(
    #         mongo_conn = mongo_conn,
    #         token_id = token_id,
    #         message_id = message_id
    #     )

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

    @staticmethod
    async def update_tags(
            mongo_conn: AsyncMongo,
            token_id: ObjectId | str,
            message_id: ObjectId | str,
            unset_tags: List[str] = None,
            set_tags: List[str] = None
    ) -> bool:

        # Simply call the core model:
        return await current_app.core_message_controller.update_tags(
            mongo_conn = mongo_conn,
            token_id = token_id,
            message_id = message_id,
            unset_tags = unset_tags,
            set_tags = set_tags
        )


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


if __name__ == "__main__":

    pass

    # from utils_v2.string import json
    #
    # file_options = [
    #     r"/home/developer/Downloads/recursive parts parse - 20241210.json",
    #     r"/home/developer/Downloads/recursive parts parse (no attachment) - 20241210.json",
    # ]
    #
    # raw_mail_json = json.from_file(file_options[1])
    # print("FROM FILE:", json.to_string(raw_mail_json["payload"]))
    # print("\n\n---------\n\n")
    # mail_controller = MailController()
    # print(json.to_string(mail_controller.drop_attachments(raw_mail_json["payload"])))
