from datetime import timedelta

import pytest
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.db import IntegrityError
from django.utils import timezone

from apps.hr.models import Contract, Employee, LeaveRequest
from apps.events.models import Event, EventTeam
from apps.scheduler import reminders
from apps.scheduler.models import ReminderLog
from apps.tasks.models import Task
from apps.companies.models import Organization, OrganizationMembership


@pytest.fixture
def organization(db):
    return Organization.objects.create(name="Test Org")


@pytest.fixture
def user(db, organization):
    from apps.hr.signals import ensure_employee_stub
    User = get_user_model()
    u = User.objects.create_user(email="sched@example.com", password="pass123")
    OrganizationMembership.objects.create(organization=organization, user=u)
    ensure_employee_stub(u)
    return u


@pytest.fixture
def user2(db, organization):
    from apps.hr.signals import ensure_employee_stub
    User = get_user_model()
    u = User.objects.create_user(email="sched2@example.com", password="pass123")
    OrganizationMembership.objects.create(organization=organization, user=u)
    ensure_employee_stub(u)
    return u


@pytest.fixture
def employee(user):
    # The hr post_save signal auto-creates an Employee stub per user — reuse it
    # (a second Employee for the same user violates the OneToOne constraint).
    emp = Employee.objects.get(user=user)
    emp.employee_id = "EMP-SCHED-1"
    emp.position = "Staff"
    emp.department = "Ops"
    emp.hire_date = timezone.localdate()
    emp.save()
    return emp


@pytest.mark.django_db
class TestReminderLog:
    def test_unique_constraint(self, user, organization):
        task = Task.objects.create(organization=organization, title="T", description="D", created_by=user)
        ct = ContentType.objects.get_for_model(task)
        ReminderLog.objects.create(content_type=ct, object_id=str(task.pk), reminder_key="overdue", sent_date=timezone.localdate())
        with pytest.raises(IntegrityError):
            ReminderLog.objects.create(content_type=ct, object_id=str(task.pk), reminder_key="overdue", sent_date=timezone.localdate())

    def test_get_or_create_is_idempotent(self, user, organization):
        task = Task.objects.create(organization=organization, title="T", description="D", created_by=user)
        assert reminders._already_sent_today(task, "overdue") is False
        assert reminders._already_sent_today(task, "overdue") is True


@pytest.mark.django_db
class TestRemindOverdueTasks:
    def test_notifies_overdue_assignee(self, user, user2, organization):
        Task.objects.create(
            organization=organization,
            title="Overdue", description="D", created_by=user, assignee=user2,
            due_date=timezone.now() - timedelta(days=1), status="todo",
        )
        assert reminders.remind_overdue_tasks() == 1
        assert reminders.remind_overdue_tasks() == 0  # dedup on same day

    def test_excludes_done_tasks(self, user, user2, organization):
        Task.objects.create(
            organization=organization,
            title="Done", description="D", created_by=user, assignee=user2,
            due_date=timezone.now() - timedelta(days=1), status="done",
        )
        assert reminders.remind_overdue_tasks() == 0

    def test_excludes_future_due_date(self, user, user2, organization):
        Task.objects.create(
            organization=organization,
            title="Future", description="D", created_by=user, assignee=user2,
            due_date=timezone.now() + timedelta(days=1), status="todo",
        )
        assert reminders.remind_overdue_tasks() == 0


@pytest.mark.django_db
class TestRemindUpcomingEvents:
    def test_notifies_team_member_24h_out(self, user, user2, organization):
        event = Event.objects.create(
            organization=organization,
            name="Conf", event_type="conference", organizer=user, status="published",
            start_date=timezone.now() + timedelta(hours=24),
            end_date=timezone.now() + timedelta(hours=30),
        )
        EventTeam.objects.create(event=event, user=user2)
        assert reminders.remind_upcoming_events() == 1
        assert reminders.remind_upcoming_events() == 0  # dedup

    def test_excludes_outside_window(self, user, user2, organization):
        event = Event.objects.create(
            organization=organization,
            name="Later", event_type="conference", organizer=user, status="published",
            start_date=timezone.now() + timedelta(hours=48),
            end_date=timezone.now() + timedelta(hours=50),
        )
        EventTeam.objects.create(event=event, user=user2)
        assert reminders.remind_upcoming_events() == 0


@pytest.mark.django_db
class TestRemindExpiringContracts:
    def test_notifies_at_30_and_7_days(self, employee):
        today = timezone.localdate()
        Contract.objects.create(
            employee=employee, contract_type="full_time", status="active",
            start_date=today - timedelta(days=300), end_date=today + timedelta(days=30),
        )
        assert reminders.remind_expiring_contracts() == 1
        assert reminders.remind_expiring_contracts() == 0  # dedup same threshold

    def test_excludes_off_threshold(self, employee):
        today = timezone.localdate()
        Contract.objects.create(
            employee=employee, contract_type="full_time", status="active",
            start_date=today - timedelta(days=300), end_date=today + timedelta(days=29),
        )
        assert reminders.remind_expiring_contracts() == 0


@pytest.mark.django_db
class TestRemindApproachingLeave:
    def test_notifies_employee_and_manager(self, user, user2):
        manager_employee = Employee.objects.get(user=user2)
        manager_employee.employee_id = "EMP-MGR-1"
        manager_employee.position = "Manager"
        manager_employee.department = "Ops"
        manager_employee.hire_date = timezone.localdate()
        manager_employee.save()

        employee = Employee.objects.get(user=user)
        employee.employee_id = "EMP-STAFF-1"
        employee.position = "Staff"
        employee.department = "Ops"
        employee.hire_date = timezone.localdate()
        employee.manager = manager_employee
        employee.save()

        tomorrow = timezone.localdate() + timedelta(days=1)
        LeaveRequest.objects.create(
            employee=employee, leave_type="annual", status="approved",
            start_date=tomorrow, end_date=tomorrow + timedelta(days=2),
        )
        assert reminders.remind_approaching_leave() == 1
        assert reminders.remind_approaching_leave() == 0  # dedup

    def test_excludes_pending_leave(self, employee):
        tomorrow = timezone.localdate() + timedelta(days=1)
        LeaveRequest.objects.create(
            employee=employee, leave_type="annual", status="pending",
            start_date=tomorrow, end_date=tomorrow + timedelta(days=2),
        )
        assert reminders.remind_approaching_leave() == 0
