"""Recruiter-wise Work — per-recruiter workload & pipeline metrics for
managers (TA / Hiring Manager) and admins, plus a recruiter's own view.

GET /api/[v1/]dashboard/recruiter-work/
    ?date_from=YYYY-MM-DD & date_to=YYYY-MM-DD & department=<dept>

This is an ADDITIVE, read-only reporting endpoint. It changes no existing
workflow, model, permission or API. Visibility is enforced SERVER-SIDE off the
logged-in user via the same JD scope used everywhere else (`_visible_jds`):

    Admin              -> all JDs  -> every recruiter.
    TA / other manager -> all JDs  -> every recruiter (org-wide oversight).
    Hiring Manager     -> ONLY the JDs assigned to them -> only the recruiters
                          working on those JDs, scored on those JDs.
    Recruiter          -> ONLY their own assigned/created JDs -> just themselves.

So a Hiring Manager can never see another Hiring Manager's recruiters or their
work, whether via the UI or a direct API call / URL change.

Metric definitions (derived live from existing data — no schema change):
    total_assigned_jds / active_jds / closed_jds
        distinct JDs assigned to the recruiter (JDRecruiterAssignment) within
        the caller's scope; active = JD status Published, closed = Closed.
    total_candidates .. candidates_joined
        applications the recruiter ADDED (JobApplication.created_by) on the
        in-scope JDs, bucketed by pipeline stage using "reached-or-beyond"
        semantics (mirrors the PM dashboard's classifier):
            screened    -> reached the `qualifying` stage or beyond
            shortlisted -> reached the `selected`  stage or beyond
            offered     -> reached the `offered`   stage or beyond
            joined      -> current stage outcome WON (`placed`)
            rejected    -> current stage outcome LOST (any rejection stage)
    pending_tasks   applications still IN_PROGRESS (awaiting recruiter action).
    current_workload active (open/Published) JDs the recruiter is assigned to.
    last_activity   most recent AuditLog entry for the recruiter (or null).
"""

from django.contrib.auth import get_user_model
from django.db.models import Count, Max, Q
from django.utils.dateparse import parse_date
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView

from core.responses import success_response, error_response

from apps.audit_logs.models import AuditLog
from apps.jobs.models import JDRecruiterAssignment
from apps.jobs.views import _visible_jds
from apps.pipeline.models import JobApplication, PipelineStage

User = get_user_model()

WON = PipelineStage.Outcome.WON
LOST = PipelineStage.Outcome.LOST
IN_PROGRESS = PipelineStage.Outcome.IN_PROGRESS

# LOST stages that can only be reached after a given milestone — lets a
# rejected/declined candidate still count toward the earlier buckets they must
# have passed through (no per-stage history is stored).
OFFER_LOST = {"offer_declined", "offered_not_joined"}
POST_SUBMIT_LOST = OFFER_LOST | {"client_declined"}


def _funnel_hits(stage, thresholds):
    """Buckets one application counts toward, by its CURRENT stage. Uses
    'reached-or-beyond' semantics so counts never shrink as a candidate
    advances."""
    if stage is None:
        return ()
    lost = stage.outcome == LOST
    won = stage.outcome == WON
    hits = []

    def reached(threshold, lost_codes):
        if won:
            return True
        if lost:
            return stage.code in lost_codes
        return threshold is not None and stage.sort_order >= threshold

    if reached(thresholds["screened"], POST_SUBMIT_LOST):
        hits.append("screened")
    if reached(thresholds["shortlisted"], OFFER_LOST):
        hits.append("shortlisted")
    if reached(thresholds["offered"], OFFER_LOST):
        hits.append("offered")
    if won:
        hits.append("joined")
    if lost:
        hits.append("rejected")
    return hits


class RecruiterWorkView(APIView):
    """Recruiter-wise work table. Server-side scoped by the caller's role."""

    permission_classes = [IsAuthenticated]

    def get(self, request):
        user = request.user
        role = (user.role or "").upper()
        # Only admins, managers (TA / Hiring / Project) and recruiters may see
        # recruiter-wise work. Everyone else (interviewer, candidate, …) is
        # denied — enforced here so a direct API call can't bypass the UI.
        if not (role == "ADMIN" or "MANAGER" in role or role == "RECRUITER"):
            return error_response("You do not have permission to view recruiter work.", status_code=403)

        # Scope = exactly the JDs this user may see (shared visibility rule).
        job_ids = list(_visible_jds(user).values_list("id", flat=True))

        # Optional date range narrows the candidate metrics only.
        date_from = parse_date((request.query_params.get("date_from") or "").strip())
        date_to = parse_date((request.query_params.get("date_to") or "").strip())

        # Recruiter set, by role:
        #   Recruiter       -> only themselves.
        #   Hiring Manager  -> only recruiters working on the HM's assigned JDs
        #                      (assigned to one, or having added candidates to
        #                      one). Recruiters with no link to the HM's JDs are
        #                      never shown.
        #   Admin / other managers (TA) -> EVERY recruiter in the organization,
        #                      including those with no current assignment (they
        #                      show zeroed metrics).
        if role == "RECRUITER":
            recruiters_qs = User.objects.filter(id=user.id, role="RECRUITER")
        elif role == "HIRING_MANAGER":
            recruiter_ids = set(
                JDRecruiterAssignment.objects.filter(jd_id__in=job_ids)
                .values_list("recruiter_id", flat=True)
            )
            recruiter_ids |= set(
                JobApplication.objects.filter(job_id__in=job_ids, created_by__isnull=False)
                .values_list("created_by_id", flat=True)
            )
            recruiters_qs = User.objects.filter(id__in=recruiter_ids, role="RECRUITER")
        else:
            recruiters_qs = User.objects.filter(role="RECRUITER")

        recruiters = list(recruiters_qs.order_by("full_name", "email"))

        dept = (request.query_params.get("department") or "").strip()
        if dept:
            recruiters = [r for r in recruiters if (r.department or "") == dept]
        rec_ids = [r.id for r in recruiters]

        # Assigned-JD counts per recruiter, within scope.
        assigned = {
            row["recruiter_id"]: row
            for row in JDRecruiterAssignment.objects.filter(
                jd_id__in=job_ids, recruiter_id__in=rec_ids
            ).values("recruiter_id").annotate(
                total=Count("jd_id", distinct=True),
                active=Count("jd_id", filter=Q(jd__status="Published"), distinct=True),
                closed=Count("jd_id", filter=Q(jd__status="Closed"), distinct=True),
            )
        }

        # Candidate funnel per recruiter (attributed by who added the candidate).
        stage_by_code = {s.code: s for s in PipelineStage.objects.filter(is_active=True)}
        thresholds = {
            "screened": getattr(stage_by_code.get("qualifying"), "sort_order", None),
            "shortlisted": getattr(stage_by_code.get("selected"), "sort_order", None),
            "offered": getattr(stage_by_code.get("offered"), "sort_order", None),
        }
        apps_qs = JobApplication.objects.filter(
            job_id__in=job_ids, created_by_id__in=rec_ids, candidate__is_deleted=False
        ).select_related("stage")
        if date_from:
            apps_qs = apps_qs.filter(created_at__date__gte=date_from)
        if date_to:
            apps_qs = apps_qs.filter(created_at__date__lte=date_to)

        funnel = {
            rid: {"total": 0, "screened": 0, "shortlisted": 0, "rejected": 0,
                  "offered": 0, "joined": 0, "in_progress": 0}
            for rid in rec_ids
        }
        for app in apps_qs:
            f = funnel.get(app.created_by_id)
            if f is None:
                continue
            f["total"] += 1
            for h in _funnel_hits(app.stage, thresholds):
                f[h] += 1
            if app.stage is not None and app.stage.outcome == IN_PROGRESS:
                f["in_progress"] += 1

        # Last activity per recruiter (most recent audit-log entry).
        last_act = {
            row["user_id"]: row["last"]
            for row in AuditLog.objects.filter(user_id__in=rec_ids)
            .values("user_id").annotate(last=Max("created_at"))
        }

        rows = []
        for r in recruiters:
            a = assigned.get(r.id, {})
            f = funnel.get(r.id, {})
            active_jds = a.get("active", 0)
            la = last_act.get(r.id)
            rows.append({
                "recruiter_id": r.id,
                "recruiter_name": r.full_name or r.email.split("@")[0],
                "email": r.email,
                "employee_id": r.employee_id or "",
                "department": r.department or "",
                "total_assigned_jds": a.get("total", 0),
                "active_jds": active_jds,
                "closed_jds": a.get("closed", 0),
                "total_candidates": f.get("total", 0),
                "candidates_screened": f.get("screened", 0),
                "candidates_shortlisted": f.get("shortlisted", 0),
                "candidates_rejected": f.get("rejected", 0),
                "candidates_offered": f.get("offered", 0),
                "candidates_joined": f.get("joined", 0),
                "pending_tasks": f.get("in_progress", 0),
                "current_workload": active_jds,
                "last_activity": la.isoformat() if la else None,
                "status": "Active" if r.is_active else "Inactive",
            })

        return success_response({"recruiters": rows})
