import json
from datetime import datetime
from typing import List

from dotenv import load_dotenv
from email_message_aws_ses_local.ses_email import EmailMessageAwsSesLocal
from label_message_local.LabelConstants import LabelsLocalConstants
from label_message_local.LabelMessage import LabelsMessageLocal
from logger_local.Logger import Logger
from message_local.ChannelProviderConstants import (
    AWS_EMAIL, AWS_SMS_MESSAGE_PROVIDER_ID, INFORU_MESSAGE_PROVIDER_ID,
    VONAGE_MESSAGE_PROVIDER_ID)
from message_local.MessageChannels import MessageChannel
from message_local.MessageImportance import MessageImportance
from message_local.MessageLocal import MessageLocal
from message_local.Recipient import Recipient
from queue_worker_local.queue_worker import QueueWorker
from sms_message_aws_sns_local.sms_message_aws_sns import SmsMessageAwsSnsLocal
from whataspp_inforu_local.WhatsAppMessageInforuLocal import \
    WhatsAppMessageInforuLocal
from whatsapp_message_vonage_local.vonage_whatsapp_message_local import \
    WhatsAppMessageVonageLocal

from .MessagesLocalConstants import (MESSAGE_ACTION_ID, MESSAGES_API_TYPE_ID,
                                     logger_object_message)

load_dotenv()

logger = Logger.create_logger(object=logger_object_message)


class MessagesLocal:
    # no classes type, so the worker can init it with json.
    def __init__(self, default_original_body: str = None, default_subject: str = None,
                 default_importance: int = MessageImportance.MEDIUM.value, to_recipients: List[dict] = None):
        self.to_recipients = to_recipients

        self.default_original_body = default_original_body
        self.default_subject = default_subject
        self.default_importance = default_importance
        self.label_crud = LabelsMessageLocal()
        self.queue_worker = QueueWorker(schema_name="message", table_name="message_table",
                                        view_name="message_outbox_view", id_column_name="message_id")

    # This method should be used by Queue Worker
    def send_sync(self, message_id: int, campaign_id: int,
                  to_recipients: List[dict] = None,
                  request_datetime: str = None,
                  importance: int = None,
                  requested_message_type: int = None,
                  sender_profile_id: int = None) -> None:
        """send method"""
        details = {
            "message_id": message_id,
            "campaign_id": campaign_id,
            "to_recipients": to_recipients,
            "request_datetime": request_datetime,
            "importance": importance,
            "requested_message_type": requested_message_type
        }
        logger.start(object=details)

        to_recipients = self._get_recipients(to_recipients or self.to_recipients)
        importance = MessageImportance(importance or self.default_importance)

        for recipient in to_recipients:
            init_kwargs = {}
            send_kwargs = {}
            # the __class__ can be set only once, but for different recipients we might want different classes
            message_local = MessageLocal(message_id=message_id,
                                         original_body=self.default_original_body,
                                         original_subject=self.default_subject,
                                         campaign_id=campaign_id,
                                         to_recipients=to_recipients,
                                         importance=importance,
                                         api_type_id=MESSAGES_API_TYPE_ID,
                                         sender_profile_id=sender_profile_id)

            message_recipient_channel = message_local.get_message_channel(recipient)
            message_recipient_provider_id = message_local.get_message_provider_id(message_recipient_channel, recipient)

            body = message_local.get_body_after_text_template(recipient, message_recipient_channel)

            if (message_recipient_channel == MessageChannel.SMS and
                    message_recipient_provider_id == AWS_SMS_MESSAGE_PROVIDER_ID):
                message_local.__class__ = SmsMessageAwsSnsLocal
            elif message_recipient_channel == MessageChannel.WHATSAPP:
                if message_recipient_provider_id == INFORU_MESSAGE_PROVIDER_ID:
                    message_local.__class__ = WhatsAppMessageInforuLocal
                elif message_recipient_provider_id == VONAGE_MESSAGE_PROVIDER_ID:
                    message_local.__class__ = WhatsAppMessageVonageLocal
                else:
                    raise Exception("Don't know which WhatsAppMessageLocal class to use "
                                    f"(provider_id: {message_recipient_provider_id})")
            elif (message_recipient_channel == MessageChannel.EMAIL and
                  message_recipient_provider_id == AWS_EMAIL):
                init_kwargs = {"subject": message_local.get_subject_after_html_template()}
                message_local.__class__ = EmailMessageAwsSesLocal

            else:
                # TODO We need to add to the message Recipient country, message plain text length after template,
                #  message HTML length after template, number of attachments, attachments' types and sizes.
                data = {"channel_id": message_recipient_channel,
                        "provider_id": message_recipient_provider_id,
                        "recipient": recipient,
                        "body": body,
                        "body_length": len(body),
                        "importance": importance,
                        "campaign_id": campaign_id
                        }
                error_message = "Don't know which MessageLocal class to use."
                logger.error(error_message, object=data)
                raise Exception(error_message + " Data: " + str(data))
            message_local.__init__(**init_kwargs)  # noqa, calling the son's init
            message_local.send(body=body, to_recipients=[recipient], **send_kwargs)  # noqa, calling the son's send

        logger.end()

    # This method will push the messages to the queue in message_outbox_table
    def send_scheduled(self, campaign_id: int,
                       to_recipients: List[Recipient] = None,
                       cc_recipients: List[Recipient] = None,
                       bcc_recipients: List[Recipient] = None,
                       request_datetime: datetime = None,
                       importance: MessageImportance = None,
                       requested_message_type: MessageChannel = None,
                       start_timestamp: datetime | str = datetime.now(),
                       sender_profile_id: int = None) -> list[int]:
        """The message will be sent any time between start_timestamp and end_timestamp
        For every bcc_recipient, a message will be pushed to the queue with the same parameters
        (message_id per bcc_recipient, or one if none provided)
        """
        importance = importance or MessageImportance(self.default_importance)
        message_ids = []
        for bcc_recipient in bcc_recipients or [None]:
            function_parameters_json = {
                "campaign_id": campaign_id,
                "to_recipients": self._recipients_to_json(to_recipients),
                "cc_recipients": self._recipients_to_json(cc_recipients),
                "bcc_recipients": bcc_recipient.to_json() if bcc_recipient else None,
                "request_datetime": str(request_datetime),
                "importance": importance.value,
                "requested_message_type": requested_message_type.value if requested_message_type else None,
                "start_timestamp": start_timestamp,
                "sender_profile_id": sender_profile_id
            }
            class_parameters_json = {"default_original_body": self.default_original_body,
                                     "default_subject": self.default_subject,
                                     "default_importance": self.default_importance,
                                     "to_recipients": self.to_recipients
                                     }
            message_id = self.queue_worker.push({"function_parameters_json": function_parameters_json,
                                                 "class_parameters_json": class_parameters_json,
                                                 "action_id": MESSAGE_ACTION_ID})
            function_parameters_json["message_id"] = message_id
            # update function_parameters_json in the queue
            self.queue_worker.update_by_id(id_column_value=message_id,
                                           data_json={"function_parameters_json": json.dumps(function_parameters_json)})
            try:
                self.label_crud.add_label(label_id=LabelsLocalConstants.MESSAGE_OUTBOX_LABEL_ID, message_id=message_id)
            except Exception as e:
                logger.exception("Failed to add label to message", object=e)
            logger.info("Message pushed to the queue successfully", object={"message_id": message_id})
            message_ids.append(message_id)
        return message_ids

    @staticmethod
    def _get_recipients(recipients: List[dict]) -> List[Recipient] | List | None:
        """get recipients"""
        if not recipients:
            return recipients  # None or []
        return [Recipient(**recipient) for recipient in recipients]

    @staticmethod
    def _recipients_to_json(recipients: List[Recipient]) -> List[dict] | List | None:
        """recipients to json"""
        if not recipients:
            return recipients
        return [recipient.to_json() for recipient in recipients]
