import logging
from datetime import timedelta

from celery import shared_task
from django.contrib.auth import get_user_model
from django.contrib.contenttypes.models import ContentType
from django.utils import timezone
from django.urls import reverse

from apps.core.notifications.emailing import rims_email_context
from apps.core.notifications.models import Notification
from apps.core.notifications.services import bulk_notify, send_notification
from apps.core.permissions.roles import UserRole
from apps.rims.projects.models import Milestone, Project

logger = logging.getLogger(__name__)
User = get_user_model()


@shared_task
def flag_overdue_milestones():
    """Mark projects at-risk when milestone report overdue >7 days; notify grantees and M&E (PRD §6.1)."""
    cutoff = timezone.now().date() - timedelta(days=7)
    overdue = (
        Milestone.objects.filter(
            report_due=True,
            report_submitted_at__isnull=True,
            status__in=[Milestone.Status.PLANNED, Milestone.Status.REVISION_REQUIRED, Milestone.Status.OVERDUE],
            due_date__lt=cutoff,
        )
        .select_related("project__award__application__applicant")
        .order_by("project_id", "due_date")
    )
    seen_projects: set[int] = set()
    at_risk_count = 0
    grantee_notices = 0
    for ms in overdue:
        project = ms.project
        if project.pk in seen_projects:
            continue
        seen_projects.add(project.pk)
        if project.status == Project.Status.CLOSED:
            continue
        if ms.status != Milestone.Status.OVERDUE:
            ms.status = Milestone.Status.OVERDUE
            ms.save(update_fields=["status"])
        if project.status != Project.Status.AT_RISK:
            project.status = Project.Status.AT_RISK
            project.save(update_fields=["status", "updated_at"])
            at_risk_count += 1

        applicant = project.award.application.applicant
        ct = ContentType.objects.get_for_model(Project)
        if Notification.objects.filter(
            recipient=applicant,
            verb=Notification.Verb.MILESTONE_OVERDUE,
            object_id=str(project.pk),
            created_at__gte=timezone.now() - timedelta(days=7),
        ).exists():
            continue
        ctx = rims_email_context(
            subject=f"Overdue milestone report: {project.title}",
            headline="Milestone report is overdue",
            action_path=reverse("rims_projects:project_detail", kwargs={"pk": project.pk}),
            action_label="Open project",
            preheader="Submit your milestone report as soon as possible.",
        )
        send_notification(
            applicant,
            (
                f'Your milestone report for "{ms.name}" ({project.title}) was due {ms.due_date} '
                "and is more than 7 days overdue. Please submit immediately."
            ),
            verb=Notification.Verb.MILESTONE_OVERDUE,
            content_type=ct,
            object_id=str(project.pk),
            email_context=ctx,
            action_url=reverse("rims_projects:project_detail", kwargs={"pk": project.pk}),
        )
        grantee_notices += 1

    if seen_projects:
        mel_users = list(
            User.objects.filter(is_active=True, role=UserRole.MEL_OFFICER).distinct()[:500]
        )
        if mel_users:
            n = len(seen_projects)
            bulk_notify(
                mel_users,
                (
                    f"RIMS: {n} project(s) have milestone reports overdue by more than 7 days "
                    "(recipients notified). Review the projects module for at-risk items."
                ),
                verb=Notification.Verb.MILESTONE_OVERDUE,
                email_context=rims_email_context(
                    subject="RIMS: overdue milestone reports",
                    headline="Compliance alert",
                    action_path=reverse("rims_projects:project_list"),
                    action_label="View projects",
                    preheader="Milestone reporting overdue across active projects.",
                ),
                action_url=reverse("rims_projects:project_list"),
            )

    logger.info(
        "flag_overdue_milestones: at_risk_updates=%s grantee_emails=%s projects=%s",
        at_risk_count,
        grantee_notices,
        len(seen_projects),
    )


@shared_task
def remind_upcoming_milestone_reports():
    """Notify grantees when a reportable milestone is due within the configured
    warn window (PRD §6.1 + FRFA-AA014). The window comes from
    ``operations.ReminderConfig.milestone_warn_days_before`` so admins can
    tune the cadence without a code change. Falls back to 14 days when no
    config row exists."""
    from apps.rims.operations.models import ReminderConfig

    cfg = ReminderConfig.objects.first()
    warn_days = cfg.milestone_warn_days_before if cfg else 14
    today = timezone.now().date()
    horizon = today + timedelta(days=warn_days)
    ct = ContentType.objects.get_for_model(Milestone)
    qs = Milestone.objects.filter(
        report_due=True,
        report_submitted_at__isnull=True,
        status__in=[Milestone.Status.PLANNED, Milestone.Status.REVISION_REQUIRED, Milestone.Status.OVERDUE],
        due_date__gte=today,
        due_date__lte=horizon,
    ).select_related("project__award__application__applicant", "project")
    count = 0
    for ms in qs:
        applicant = ms.project.award.application.applicant
        if Notification.objects.filter(
            recipient=applicant,
            verb=Notification.Verb.DEADLINE_REMINDER,
            object_id=str(ms.pk),
            created_at__gte=timezone.now() - timedelta(days=5),
        ).exists():
            continue
        ctx = rims_email_context(
            subject=f"Milestone due: {ms.name}",
            headline="Upcoming milestone report",
            action_path=reverse("rims_projects:project_detail", kwargs={"pk": ms.project.pk}),
            action_label="View project",
            preheader=f"Due {ms.due_date}",
        )
        send_notification(
            applicant,
            (
                f'Reminder: milestone report due {ms.due_date} for "{ms.name}" '
                f'({ms.project.title}). Please submit before the deadline.'
            ),
            verb=Notification.Verb.DEADLINE_REMINDER,
            content_type=ct,
            object_id=str(ms.pk),
            email_context=ctx,
            action_url=reverse("rims_projects:project_detail", kwargs={"pk": ms.project.pk}),
        )
        count += 1
    logger.info("remind_upcoming_milestone_reports: sent %s notifications", count)
