from datetime import timedelta
from decimal import Decimal
from unittest.mock import patch

from django.contrib.auth import get_user_model
from django.core import mail
from django.test import TestCase, override_settings
from django.urls import reverse
from django.utils import timezone

from billing.models import Renewal
from clients.models import Client, ClientContact
from core.choices import BillingCycle, ServiceStatus, ServiceType
from core.models import PortalConfiguration
from notifications.models import EmailSuppression, NotificationAttempt, NotificationLog, NotificationTemplate
from notifications.scheduling import ensure_notification_schedule
from notifications.workflows import (
    create_notification_from_template,
    prepare_due_notifications,
    retry_failed_notifications,
    send_notification,
    send_scheduled_notifications,
)
from services.models import Service


@override_settings(EMAIL_BACKEND="django.core.mail.backends.locmem.EmailBackend")
class NotificationWorkflowTests(TestCase):
    def setUp(self):
        self.client_record = Client.objects.create(name="Cliente Teste", email="cliente@example.com")
        self.service = Service.objects.create(
            client=self.client_record,
            service_type=ServiceType.DOMAIN,
            name="Domínio exemplo.pt",
            status=ServiceStatus.ACTIVE,
            next_renewal_date=timezone.localdate() + timedelta(days=30),
            billing_cycle=BillingCycle.ANNUAL,
            sale_price=Decimal("30.00"),
        )
        self.renewal = Renewal.objects.create(
            service=self.service,
            due_date=self.service.next_renewal_date,
            sale_amount=self.service.sale_price,
        )
        self.template = NotificationTemplate.objects.create(
            code="test-renewal-30",
            notification_type=NotificationTemplate.Type.RENEWAL_30,
            subject_template="Renovação de {servico}",
            body_template="Olá {cliente}. Valor: {valor}.",
            html_body_template="<p>Olá <strong>{cliente}</strong>. Valor: {valor}.</p>",
        )

    def test_create_and_send_notification(self):
        notification, created = create_notification_from_template(self.renewal, NotificationTemplate.Type.RENEWAL_30)
        self.assertTrue(created)
        send_notification(notification)
        notification.refresh_from_db()
        self.renewal.refresh_from_db()
        self.assertEqual(notification.status, NotificationLog.Status.SENT)
        self.assertEqual(notification.attempts_count, 1)
        self.assertEqual(notification.attempts.count(), 1)
        self.assertEqual(len(mail.outbox), 1)
        self.assertEqual(mail.outbox[0].to, ["cliente@example.com"])
        self.assertIsNotNone(self.renewal.notice_30_sent_at)

    def test_test_mode_redirects_and_preserves_original_recipient(self):
        PortalConfiguration.objects.create(
            company_name="Portal Teste",
            email_test_mode=True,
            email_test_recipient="admin-test@example.com",
        )
        ClientContact.objects.create(
            client=self.client_record,
            name="Financeiro",
            email="financeiro@example.com",
            receives_billing=True,
        )
        notification, _ = create_notification_from_template(self.renewal, NotificationTemplate.Type.RENEWAL_30)
        send_notification(notification)
        notification.refresh_from_db()
        self.assertEqual(notification.delivery_mode, NotificationLog.DeliveryMode.TEST)
        self.assertEqual(notification.original_recipient, "cliente@example.com")
        self.assertEqual(mail.outbox[0].to, ["admin-test@example.com"])
        self.assertEqual(mail.outbox[0].cc, [])
        self.assertTrue(mail.outbox[0].subject.startswith("[TESTE para cliente@example.com]"))

    def test_test_mode_without_safe_recipient_fails_closed(self):
        PortalConfiguration.objects.create(company_name="Portal Teste", email_test_mode=True)
        notification, _ = create_notification_from_template(self.renewal, NotificationTemplate.Type.RENEWAL_30)
        delivered = send_notification(notification, raise_errors=False)
        self.assertEqual(delivered.status, NotificationLog.Status.FAILED)
        self.assertIn("destinatário de testes", delivered.error_message)
        self.assertEqual(len(mail.outbox), 0)

    def test_suppressed_recipient_is_not_sent(self):
        EmailSuppression.objects.create(email="cliente@example.com", reason=EmailSuppression.Reason.BOUNCED)
        notification, _ = create_notification_from_template(self.renewal, NotificationTemplate.Type.RENEWAL_30)
        delivered = send_notification(notification, raise_errors=False)
        self.assertEqual(delivered.status, NotificationLog.Status.SUPPRESSED)
        self.assertEqual(delivered.attempts.first().result, NotificationAttempt.Result.SUPPRESSED)
        self.assertEqual(len(mail.outbox), 0)

    def test_failed_delivery_is_recorded_and_can_be_retried(self):
        notification, _ = create_notification_from_template(self.renewal, NotificationTemplate.Type.RENEWAL_30)
        with patch("notifications.workflows.EmailMultiAlternatives.send", side_effect=RuntimeError("SMTP indisponível")):
            delivered = send_notification(notification, raise_errors=False)
        delivered.refresh_from_db()
        self.assertEqual(delivered.status, NotificationLog.Status.FAILED)
        self.assertEqual(delivered.attempts_count, 1)
        self.assertIsNotNone(delivered.next_retry_at)
        delivered.next_retry_at = timezone.now() - timedelta(minutes=1)
        delivered.save(update_fields=["next_retry_at"])
        result = retry_failed_notifications()
        self.assertEqual(result["sent"], 1)
        delivered.refresh_from_db()
        self.assertEqual(delivered.status, NotificationLog.Status.SENT)
        self.assertEqual(delivered.attempts_count, 2)

    def test_prepare_notifications_is_idempotent(self):
        first = prepare_due_notifications(send=False, automatic=False)
        second = prepare_due_notifications(send=False, automatic=False)
        self.assertEqual(first["created"], 1)
        self.assertEqual(second["existing"], 1)
        self.assertEqual(NotificationLog.objects.count(), 1)

    def test_scheduled_queue_sends_due_messages(self):
        notification, _ = create_notification_from_template(
            self.renewal,
            NotificationTemplate.Type.RENEWAL_30,
            status=NotificationLog.Status.SCHEDULED,
            scheduled_for=timezone.now() - timedelta(minutes=1),
        )
        result = send_scheduled_notifications()
        notification.refresh_from_db()
        self.assertEqual(result["sent"], 1)
        self.assertEqual(notification.status, NotificationLog.Status.SENT)


class CommunicationCenterTests(TestCase):
    def setUp(self):
        user_model = get_user_model()
        self.user = user_model.objects.create_superuser("admin", "admin@example.com", "password")
        self.client.force_login(self.user)

    def test_communications_center_opens(self):
        response = self.client.get(reverse("communications_center"))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "Centro de comunicações")

    def test_schedule_setup_creates_three_tasks(self):
        tasks = ensure_notification_schedule()
        self.assertEqual(len(tasks), 3)
