from datetime import date, datetime
from types import SimpleNamespace
from unittest import TestCase
from unittest.mock import patch

from app.services import (
    _has_new_overdue_business_day,
    _send_task_assignment_email,
    business_days_between,
    send_overdue_alerts,
    task_progress_metrics,
)


class BusinessDayTests(TestCase):
    def test_weekends_are_not_counted(self):
        self.assertEqual(business_days_between(date(2026, 8, 7), date(2026, 8, 10)), 2)
        self.assertEqual(business_days_between(date(2026, 8, 8), date(2026, 8, 9)), 0)

    def test_overdue_increases_only_on_business_days(self):
        task = SimpleNamespace(
            started_at=datetime(2026, 8, 7, 9, 0),
            duration_days=1,
            overdue_alert_sent_at=None,
        )

        sunday = task_progress_metrics(task, datetime(2026, 8, 9, 12, 0))
        monday = task_progress_metrics(task, datetime(2026, 8, 10, 12, 0))
        tuesday = task_progress_metrics(task, datetime(2026, 8, 11, 12, 0))

        self.assertEqual(sunday["overdue_days"], 0)
        self.assertEqual(monday["overdue_days"], 1)
        self.assertEqual(tuesday["overdue_days"], 2)

    def test_alert_waits_for_a_new_overdue_business_day(self):
        task = SimpleNamespace(
            started_at=datetime(2026, 8, 5, 9, 0),
            duration_days=2,
            overdue_alert_sent_at=datetime(2026, 8, 7, 10, 0),
        )

        saturday_overdue = task_progress_metrics(task, datetime(2026, 8, 8, 12, 0))["overdue_days"]
        monday_overdue = task_progress_metrics(task, datetime(2026, 8, 10, 12, 0))["overdue_days"]

        self.assertFalse(_has_new_overdue_business_day(task, saturday_overdue))
        self.assertTrue(_has_new_overdue_business_day(task, monday_overdue))


class AssignmentNotificationTests(TestCase):
    @patch("app.services._safe_send_email")
    def test_assigned_user_and_all_other_admins_are_notified(self, send_email):
        assigned = SimpleNamespace(username="responsable", email="user@example.com")
        admins = [
            SimpleNamespace(username="admin1", email="admin1@example.com"),
            SimpleNamespace(username="admin2", email="admin2@example.com"),
            SimpleNamespace(username="duplicado", email="USER@example.com"),
        ]
        task = SimpleNamespace(
            title="Preparar informe",
            priority="high",
            duration_days=3,
            project_id=7,
        )
        project = SimpleNamespace(name="Proyecto CIDTI")

        _send_task_assignment_email(assigned, task, project, admins)

        recipients = [call.args[0] for call in send_email.call_args_list]
        self.assertEqual(recipients, ["user@example.com", "admin1@example.com", "admin2@example.com"])
        self.assertIn("dia(s) habil(es)", send_email.call_args_list[0].args[2])
        self.assertTrue(send_email.call_args_list[1].args[1].startswith("[Kanban][Admin]"))


class OverdueNotificationTests(TestCase):
    @patch("app.services.ProjectRepository")
    @patch("app.services.TaskRepository")
    @patch("app.services.UserRepository")
    @patch("app.services._safe_send_email", return_value=True)
    def test_each_new_overdue_business_day_notifies_user_and_admins(
        self,
        send_email,
        user_repository,
        task_repository,
        project_repository,
    ):
        assigned = SimpleNamespace(username="responsable", email="user@example.com")
        task = SimpleNamespace(
            status="in_progress",
            assigned_user=assigned,
            started_at=datetime(2026, 8, 7, 9, 0),
            duration_days=1,
            overdue_alert_sent_at=None,
            project_id=7,
            title="Preparar informe",
        )
        user_repository.return_value.list_admins.return_value = [
            SimpleNamespace(username="admin1", email="admin1@example.com"),
            SimpleNamespace(username="admin2", email="admin2@example.com"),
        ]
        project_repository.return_value.get.return_value = SimpleNamespace(name="Proyecto CIDTI")

        send_overdue_alerts(object(), [task], datetime(2026, 8, 10, 8, 0))
        self.assertEqual(
            [call.args[0] for call in send_email.call_args_list],
            ["user@example.com", "admin1@example.com", "admin2@example.com"],
        )
        self.assertEqual(task.overdue_alert_sent_at, datetime(2026, 8, 10, 8, 0))
        task_repository.return_value.update.assert_called_once_with(task)

        send_email.reset_mock()
        send_overdue_alerts(object(), [task], datetime(2026, 8, 10, 16, 0))
        send_email.assert_not_called()

        send_overdue_alerts(object(), [task], datetime(2026, 8, 11, 8, 0))
        self.assertEqual(send_email.call_count, 3)

    @patch("app.services.ProjectRepository")
    @patch("app.services.TaskRepository")
    @patch("app.services.UserRepository")
    @patch("app.services._safe_send_email", return_value=False)
    def test_failed_delivery_is_retried(
        self,
        send_email,
        user_repository,
        task_repository,
        project_repository,
    ):
        task = SimpleNamespace(
            status="in_progress",
            assigned_user=SimpleNamespace(username="responsable", email="user@example.com"),
            started_at=datetime(2026, 8, 7, 9, 0),
            duration_days=1,
            overdue_alert_sent_at=None,
            project_id=7,
            title="Preparar informe",
        )
        user_repository.return_value.list_admins.return_value = []
        project_repository.return_value.get.return_value = SimpleNamespace(name="Proyecto CIDTI")

        send_overdue_alerts(object(), [task], datetime(2026, 8, 10, 8, 0))

        self.assertIsNone(task.overdue_alert_sent_at)
        task_repository.return_value.update.assert_not_called()


if __name__ == "__main__":
    import unittest

    unittest.main()
