"""S-E (Phase 8.6) — HR Celery Tasks tests.

Covers all three scheduled tasks:
- hr.accrue_monthly_leave
- hr.check_document_expiry
- hr.sync_on_leave_status
"""

from __future__ import annotations

from datetime import date, timedelta
from decimal import Decimal
from unittest.mock import patch

import pytest

from simorgh.apps.hr_core.models import (
    Employee,
    EmployeeDocument,
    EmployeeStatus,
    LeaveBalance,
    LeaveRequest,
    LeaveRequestStatus,
    LeaveType,
)
from simorgh.apps.hr_core import services


# ---------------------------------------------------------------------------
# Shared fixtures
# ---------------------------------------------------------------------------

@pytest.fixture
def job_title(tenant_acme):
    from simorgh.apps.hr_core.models import JobTitle
    return JobTitle.objects.create(tenant=tenant_acme, title="Task Tester", level=2)


@pytest.fixture
def employee(tenant_acme, acme_tree, job_title) -> Employee:
    return services.create_employee(
        tenant_id=tenant_acme.pk,
        organization_node_id=acme_tree["root"].pk,
        first_name="Celery",
        last_name="Worker",
        hire_date=date(2022, 3, 1),
        employment_type="full_time",
    )


@pytest.fixture
def monthly_leave_type(tenant_acme) -> LeaveType:
    return services.create_leave_type(
        tenant_id=tenant_acme.pk,
        name="Monthly Accrual",
        code="MONTHLY_ACC",
        max_days_per_year=12,
        accrual_method="monthly",
    )


@pytest.fixture
def upfront_leave_type(tenant_acme) -> LeaveType:
    return services.create_leave_type(
        tenant_id=tenant_acme.pk,
        name="Upfront Leave",
        code="UPFRONT_TASK",
        max_days_per_year=21,
        accrual_method="upfront",
    )


# ===========================================================================
# hr.accrue_monthly_leave
# ===========================================================================

@pytest.mark.django_db
class TestAccrueMonthlyLeaveTask:

    def test_creates_balance_and_accrues(
        self, tenant_acme, employee, monthly_leave_type
    ):
        """Task creates a new balance record and adds monthly fraction."""
        from simorgh.apps.hr_core.tasks import accrue_monthly_leave

        today = date.today()
        year = today.year

        count = accrue_monthly_leave.apply().get()
        assert count >= 1

        balance = LeaveBalance.objects.get(
            employee=employee,
            leave_type=monthly_leave_type,
            year=year,
        )
        # 12 days / 12 months = 1.00 day per month
        assert balance.allocated == Decimal("1.00")

    def test_accrues_incrementally_on_second_run(
        self, tenant_acme, employee, monthly_leave_type
    ):
        """Running the task twice accrues twice."""
        from simorgh.apps.hr_core.tasks import accrue_monthly_leave

        today = date.today()
        year = today.year

        accrue_monthly_leave.apply().get()
        accrue_monthly_leave.apply().get()

        balance = LeaveBalance.objects.get(
            employee=employee,
            leave_type=monthly_leave_type,
            year=year,
        )
        assert balance.allocated == Decimal("2.00")

    def test_upfront_leave_type_not_accrued(
        self, tenant_acme, employee, upfront_leave_type
    ):
        """Leave types with accrual_method != 'monthly' are skipped."""
        from simorgh.apps.hr_core.tasks import accrue_monthly_leave

        accrue_monthly_leave.apply().get()

        assert not LeaveBalance.objects.filter(
            employee=employee,
            leave_type=upfront_leave_type,
        ).exists()

    def test_terminated_employee_not_accrued(
        self, tenant_acme, employee, monthly_leave_type
    ):
        """Terminated employees are not accrued."""
        from simorgh.apps.hr_core.tasks import accrue_monthly_leave

        services.terminate_employee(
            employee, termination_date=date(2025, 1, 1), reason="Test"
        )

        today = date.today()
        year = today.year
        accrue_monthly_leave.apply().get()

        assert not LeaveBalance.objects.filter(
            employee=employee,
            leave_type=monthly_leave_type,
            year=year,
        ).exists()


# ===========================================================================
# hr.check_document_expiry
# ===========================================================================

@pytest.mark.django_db
class TestCheckDocumentExpiryTask:

    def test_dispatches_event_for_expiring_document(
        self, tenant_acme, employee
    ):
        """Documents expiring within 30 days trigger an event dispatch."""
        from simorgh.apps.hr_core.tasks import check_document_expiry

        doc = services.add_employee_document(
            employee=employee,
            doc_type="id_card",
            dms_file_id="00000000-0000-0000-0000-000000000001",
            title="ID Card",
            expiry_date=date.today() + timedelta(days=15),
        )
        doc.is_verified = True
        doc.save()

        with patch("simorgh.apps.events.bus.dispatch_async") as mock_dispatch:
            count = check_document_expiry.apply().get()

        assert count >= 1
        assert mock_dispatch.called

    def test_far_future_document_not_dispatched(self, tenant_acme, employee):
        """Documents expiring more than 30 days away should not trigger an event."""
        from simorgh.apps.hr_core.tasks import check_document_expiry

        doc = services.add_employee_document(
            employee=employee,
            doc_type="passport",
            dms_file_id="00000000-0000-0000-0000-000000000002",
            title="Passport",
            expiry_date=date.today() + timedelta(days=90),
        )
        doc.is_verified = True
        doc.save()

        with patch("simorgh.apps.events.bus.dispatch_async") as mock_dispatch:
            count = check_document_expiry.apply().get()

        assert count == 0
        mock_dispatch.assert_not_called()

    def test_already_expired_document_not_dispatched(self, tenant_acme, employee):
        """Documents already past their expiry date are not dispatched (too late)."""
        from simorgh.apps.hr_core.tasks import check_document_expiry

        doc = services.add_employee_document(
            employee=employee,
            doc_type="contract",
            dms_file_id="00000000-0000-0000-0000-000000000003",
            title="Old Contract",
            expiry_date=date.today() - timedelta(days=1),
        )
        doc.is_verified = True
        doc.save()

        with patch("simorgh.apps.events.bus.dispatch_async") as mock_dispatch:
            count = check_document_expiry.apply().get()

        assert count == 0


# ===========================================================================
# hr.sync_on_leave_status
# ===========================================================================

@pytest.mark.django_db
class TestSyncOnLeaveStatusTask:

    @pytest.fixture
    def annual_leave(self, tenant_acme) -> LeaveType:
        return services.create_leave_type(
            tenant_id=tenant_acme.pk,
            name="Annual Sync Test",
            code="ANNUAL_SYNC",
            max_days_per_year=21,
            requires_approval=True,
        )

    def test_sets_employee_to_on_leave(
        self, tenant_acme, employee, annual_leave, alice
    ):
        """Employee with approved leave covering today is set to on_leave."""
        from simorgh.apps.hr_core.tasks import sync_on_leave_status

        today = date.today()
        LeaveBalance.objects.create(
            tenant_id=tenant_acme.pk,
            organization_node_id=employee.organization_node_id,
            employee=employee,
            leave_type=annual_leave,
            year=today.year,
            allocated=Decimal("5"),
        )
        req = services.create_leave_request(
            tenant_id=tenant_acme.pk,
            employee=employee,
            leave_type=annual_leave,
            from_date=today - timedelta(days=1),
            to_date=today + timedelta(days=1),
        )
        req = services.submit_leave_request(req)
        req = services.approve_leave_request(req, reviewed_by=alice, note="ok")

        # Reset status back to active to simulate stale state before task runs
        Employee.objects.filter(pk=employee.pk).update(status=EmployeeStatus.ACTIVE)

        result = sync_on_leave_status.apply().get()
        assert result["set_on_leave"] >= 1

        employee.refresh_from_db()
        assert employee.status == EmployeeStatus.ON_LEAVE

    def test_restores_stale_on_leave_employee(
        self, tenant_acme, employee
    ):
        """Employee marked on_leave with no active approved leave is restored to active."""
        from simorgh.apps.hr_core.tasks import sync_on_leave_status

        Employee.objects.filter(pk=employee.pk).update(status=EmployeeStatus.ON_LEAVE)

        result = sync_on_leave_status.apply().get()
        assert result["restored"] >= 1

        employee.refresh_from_db()
        assert employee.status == EmployeeStatus.ACTIVE

    def test_terminated_employee_not_touched(
        self, tenant_acme, employee
    ):
        """Terminated employees are never modified by the sync task."""
        from simorgh.apps.hr_core.tasks import sync_on_leave_status

        services.terminate_employee(employee, termination_date=date(2025, 1, 1))
        Employee.objects.filter(pk=employee.pk).update(status=EmployeeStatus.TERMINATED)

        sync_on_leave_status.apply().get()

        employee.refresh_from_db()
        assert employee.status == EmployeeStatus.TERMINATED
