import logging
from typing import List, Set

from django.conf import settings
from django.db import transaction

from apps.appointments.services.notification_context import AppointmentNotificationContext
from apps.appointments.templates.notification_templates import (
    in_app_message_template,
    sms_template,
    booking_email_context
)
from apps.calls.repositories import MessageRepository
from apps.calls.serializers import MessageCreateSerializer
from apps.calls.services.twilio_sms_service import SmsService
from apps.calls.services.notification_service import NotificationService
from apps.calls.services.email_service import EmailService
from apps.core.repositories import UserRepository
from apps.userprofile.services import ProfileServices

logger = logging.getLogger(__name__)


class AppointmentNotificationService:
    """
    Orchestrates all communications triggered by appointment creation.
    - In-app message
    - Notification
    - SMS (async)
    - Email (async)
    """

    @classmethod
    def process(cls, appointment, extra):
        if not appointment:
            logger.error("AppointmentNotificationService.process called with None")
            return None

        ctx = AppointmentNotificationContext.build(
            appointment=appointment,
            extra=extra
        )
        recipient = cls._get_recipient(appointment)

        service_managers = UserRepository.get_service_managers_for_company(
            company=appointment.company,
        )

        emails = cls._collect_emails(
            appointment=appointment,
            recipient=recipient,
            service_managers=service_managers,
        )

        if not recipient:
            cls._send_email(
                ctx,
                emails,
                "No Default Transfer Staff",
                f"No Default Transfer Staff for {ctx['company']}",
            )
            return None

        with transaction.atomic():
            message = cls._create_message(recipient, appointment.company, ctx)

        cls._send_in_app_notification(recipient, appointment.company, message)
        cls._send_sms(ctx)
        cls._send_email(
            ctx,
            emails,
            ProfileServices.resolve_display_name(user=recipient),
            'New Appointment Booking',
        )

        logger.info(
            "Appointment notifications processed for appointment %s",
            appointment.id,
        )

        return message

    @staticmethod
    def _get_recipient(appointment):
        recipient = appointment.company.default_transfer_staff
        if not recipient:
            logger.warning(
                f"No default_transfer_staff for company {appointment.company_id}"
            )
            return None
        return recipient

    @staticmethod
    def _create_message(recipient, company, ctx):
        body = in_app_message_template(ctx)

        payload = {
            "recipient": recipient.id,
            "customer_name": ctx["customer_name"],
            "customer_number": ctx["customer_number"],
            "subject": "Appointment Message",
            "body": body,
        }

        serializer = MessageCreateSerializer(data=payload)
        serializer.is_valid(raise_exception=True)

        message = MessageRepository.create_message(
            company=company,
            **serializer.validated_data
        )

        logger.info(
            "Message created (id=%s)",
            message.id,
        )

        return message

    @staticmethod
    def _collect_emails(appointment, recipient, service_managers) -> List[str]:
        emails: Set[str] = set()

        if settings.ALL_DEALERSHIPS_BOOKING_NOTIFY_EMAILS:
            emails.update(settings.ALL_DEALERSHIPS_BOOKING_NOTIFY_EMAILS)

        booking_email_users = UserRepository.get_booking_email_users_for_company(
            company=appointment.company
        )

        if booking_email_users:
            emails.update(
                user.email for user in booking_email_users if user.email
            )

        if recipient and recipient.email:
            emails.add(recipient.email)

        if service_managers:
            emails.update(
                manager.email for manager in service_managers if manager.email
            )

        cleaned = [
            email.strip().lower()
            for email in emails
            if email and "@" in email
        ]

        return list(set(cleaned))

    @staticmethod
    def _send_in_app_notification(recipient, company, message):

        try:
            NotificationService.send_message_notification(
                user=recipient,
                company=company,
                title=message.subject,
                message=message.body,
            )
        except Exception as exc:
            logger.exception(f"In-app notification failed {exc}")

    @staticmethod
    def _send_sms(ctx):

        try:
            sms_text = sms_template(ctx)

            SmsService.send_sms(
                to_number=ctx['bdc_number'],
                message=sms_text,
            )
        except Exception as exc:
            logger.exception(f"SMS enqueue failed {exc}")

    @staticmethod
    def _send_email(ctx, emails, full_name, subject):

        try:
            EmailService.send_email(
                subject=subject,
                html_content="appointment/booking_1.html",
                recipient_list=emails,
                bcc=settings.EMAIL_BCC,
                key=booking_email_context(ctx, full_name),
            )
        except Exception as exc:
            logger.exception(f"Email enqueue failed {exc}")
