from datetime import timedelta

from django.test import TestCase
from django.utils import timezone

from NEMO.admin import ClosureAdminForm
from NEMO.models import Alert, Closure, ClosureTime, EmailLog, User
from NEMO.tests.test_utilities import NEMOTestCaseMixin
from NEMO.utilities import EmailCategory
from NEMO.views.customization import EmailsCustomization
from NEMO.views.timed_services import do_create_closure_alerts


class ClosuresTestCase(NEMOTestCaseMixin, TestCase):
    def setUp(self):
        # We need user office email and at least one facility manager for the email to be sent
        EmailsCustomization.set(name="user_office_email_address", value="test@test.com")
        User.objects.create(
            first_name="Testy", last_name="McTester", email="testy@tester.com", is_active=True, is_facility_manager=True
        )

    def testAlertNoTemplate(self):
        # Try creating an alert with days_before set but no alert template (should fail)
        closure_admin = ClosureAdminForm({"name": "Closure fail", "alert_days_before": 1})
        self.assertFalse(closure_admin.is_valid())
        self.assertIn("alert_template", closure_admin.errors)

    def testClosureNoAlerts(self):
        # Create closure with no alert days before
        closure_no_alert = Closure.objects.create(name="Closure no alert")
        ClosureTime.objects.create(
            closure=closure_no_alert, start_time=timezone.now(), end_time=timezone.now() + timedelta(days=1)
        )
        do_create_closure_alerts()
        self.assertFalse(Alert.objects.filter(title=closure_no_alert.name).exists())

    def testClosureEndPast(self):
        # Create closure in the past, alert should not be created
        closure_no_alert = Closure.objects.create(name="Closure no alert", alert_days_before=1, alert_template="test")
        ClosureTime.objects.create(
            closure=closure_no_alert,
            start_time=timezone.now() - timedelta(days=4),
            end_time=timezone.now() - timedelta(days=2),
        )
        do_create_closure_alerts()
        self.assertFalse(Alert.objects.filter(title=closure_no_alert.name).exists())

    def testClosureAlertNow(self):
        # Create closure with alert
        closure = Closure.objects.create(name="Closure alert", alert_days_before=0, alert_template="test {{ name }}")
        start_time = timezone.now()
        end_time = start_time + timedelta(days=1)
        ClosureTime.objects.create(closure=closure, start_time=start_time, end_time=end_time)
        do_create_closure_alerts()
        alert = Alert.objects.filter(title=closure.name).first()
        self.assertEqual(alert.title, closure.name)
        self.assertEqual(alert.contents, f"test {closure.name}")
        self.assertEqual(alert.expiration_time, end_time)
        self.assertEqual(alert.debut_time, start_time)
        do_create_closure_alerts()
        self.assertEqual(Alert.objects.filter(title=closure.name).count(), 1)

    def testClosureAlertTooFarOut(self):
        # Create closure with alert
        closure = Closure.objects.create(name="Closure future alert", alert_days_before=0, alert_template="test")
        start_time = timezone.now() + timedelta(weeks=2)
        end_time = timezone.now() + timedelta(days=1)
        ClosureTime.objects.create(closure=closure, start_time=start_time, end_time=end_time)
        do_create_closure_alerts()
        self.assertFalse(Alert.objects.filter(title=closure.name).exists())

    def testClosureAlertRightOn(self):
        # Closure just a week out. Alert should be created
        closure = Closure.objects.create(name="Closure alert", alert_days_before=3, alert_template="test")
        start_time = timezone.now() + timedelta(weeks=1)
        end_time = timezone.now() + timedelta(days=1)
        ClosureTime.objects.create(closure=closure, start_time=start_time, end_time=end_time)
        do_create_closure_alerts()
        alert = Alert.objects.filter(title=closure.name).first()
        self.assertTrue(alert)
        self.assertEqual(alert.debut_time, start_time - timedelta(days=3))

    def testClosureAlertRightAfter(self):
        # Closure a week and an hour out. Alert should not be created
        closure = Closure.objects.create(name="Closure alert", alert_days_before=0, alert_template="test")
        start_time = timezone.now() + timedelta(weeks=1) + timedelta(hours=1)
        end_time = timezone.now() + timedelta(days=1)
        ClosureTime.objects.create(closure=closure, start_time=start_time, end_time=end_time)
        do_create_closure_alerts()
        self.assertFalse(Alert.objects.filter(title=closure.name).exists())

    def testClosureAlertRightAfterButDaysBefore(self):
        # Closure a week and a day out but with days_before set to 2. Alert should be created
        closure = Closure.objects.create(name="Closure alert", alert_days_before=2, alert_template="test")
        start_time = timezone.now() + timedelta(weeks=1) + timedelta(days=1)
        end_time = timezone.now() + timedelta(days=1)
        ClosureTime.objects.create(closure=closure, start_time=start_time, end_time=end_time)
        do_create_closure_alerts()
        self.assertTrue(Alert.objects.filter(title=closure.name).exists())

    def testClosureStartPastEndLater(self):
        # Create closure starting in the past but ending after now, alert should be created
        closure_overlap = Closure.objects.create(name="Closure", alert_days_before=1, alert_template="test")
        start_time = timezone.now() - timedelta(weeks=1)
        end_time = timezone.now() + timedelta(days=1)
        ClosureTime.objects.create(closure=closure_overlap, start_time=start_time, end_time=end_time)
        do_create_closure_alerts()
        self.assertTrue(Alert.objects.filter(title=closure_overlap.name).exists())

    def testMultipleClosureTimes(self):
        closure_multiple_times = Closure.objects.create(
            name="Closure multiple", alert_days_before=1, alert_template="test"
        )
        start_time = timezone.now() - timedelta(days=2)
        end_time = timezone.now() + timedelta(days=1)
        ClosureTime.objects.create(closure=closure_multiple_times, start_time=start_time, end_time=end_time)
        # Create a second closure time 4 days later
        ClosureTime.objects.create(
            closure=closure_multiple_times,
            start_time=start_time + timedelta(days=4),
            end_time=end_time + timedelta(days=4),
        )
        do_create_closure_alerts()
        # Both alerts should have been created
        alert_qs = Alert.objects.filter(title=closure_multiple_times.name).order_by("debut_time")
        self.assertEqual(alert_qs.count(), 2)
        alert1 = alert_qs.first()
        self.assertEqual(alert1.title, closure_multiple_times.name)
        self.assertEqual(alert1.debut_time, start_time - timedelta(days=1))
        self.assertEqual(alert1.expiration_time, end_time)
        alert2 = alert_qs.last()
        self.assertEqual(alert2.title, closure_multiple_times.name)
        self.assertEqual(alert2.debut_time, start_time - timedelta(days=1) + timedelta(days=4))
        self.assertEqual(alert2.expiration_time, end_time + timedelta(days=4))

    def testLastClosureEmailNotSent(self):
        # first closure time ends today, but we have one more occurrence later. no email should be sent
        closure = Closure.objects.create(
            name="Closure", alert_days_before=1, alert_template="test", notify_managers_last_occurrence=True
        )
        start_time = timezone.now() - timedelta(days=2)
        end_time = timezone.now()
        ClosureTime.objects.create(closure=closure, start_time=start_time, end_time=end_time)
        # Create a second closure time 4 days later
        ClosureTime.objects.create(
            closure=closure, start_time=start_time + timedelta(days=4), end_time=end_time + timedelta(days=4)
        )
        do_create_closure_alerts()
        self.assertFalse(
            EmailLog.objects.filter(category=EmailCategory.SYSTEM, subject=f"Last {closure.name} occurrence").exists()
        )

    def testLastClosureEmailSent(self):
        # first closure time ends earlier, last one ends today. email should be sent
        closure = Closure.objects.create(
            name="Closure", alert_days_before=1, alert_template="test", notify_managers_last_occurrence=False
        )
        start_time = timezone.now() - timedelta(weeks=2)
        end_time = timezone.now() - timedelta(weeks=1)
        ClosureTime.objects.create(closure=closure, start_time=start_time, end_time=end_time)
        # Create a second closure time ending today
        ClosureTime.objects.create(
            closure=closure, start_time=timezone.now() - timedelta(days=1), end_time=timezone.now()
        )
        do_create_closure_alerts()
        # Flag not set on closure so no email
        self.assertFalse(
            EmailLog.objects.filter(category=EmailCategory.SYSTEM, subject=f"Last {closure.name} occurrence").exists()
        )
        closure.notify_managers_last_occurrence = True
        closure.save()
        do_create_closure_alerts()
        # Now email sent
        self.assertTrue(
            EmailLog.objects.filter(category=EmailCategory.SYSTEM, subject=f"Last {closure.name} occurrence").exists()
        )

    def testLastClosureLaterNoEmailSent(self):
        # first closure time ends earlier, last one ends tomorrow. email should not be sent
        closure = Closure.objects.create(
            name="Closure", alert_days_before=1, alert_template="test", notify_managers_last_occurrence=True
        )
        start_time = timezone.now() - timedelta(weeks=2)
        end_time = timezone.now() - timedelta(weeks=1)
        ClosureTime.objects.create(closure=closure, start_time=start_time, end_time=end_time)
        # Create a second closure time ending tomorrow
        ClosureTime.objects.create(
            closure=closure, start_time=timezone.now() - timedelta(days=1), end_time=timezone.now() + timedelta(days=1)
        )
        do_create_closure_alerts()
        self.assertFalse(
            EmailLog.objects.filter(category=EmailCategory.SYSTEM, subject=f"Last {closure.name} occurrence").exists()
        )
