"""Candidate PDF Report APIs — /api/v1/reports/candidate-reports/...

Access:
  - ADMIN / *MANAGER* roles: everything (list, view, generate, regenerate, download)
  - RECRUITER: view/download only, and only candidates on JDs assigned to them
"""

from django.core.files.base import ContentFile
from django.db.models import Avg, OuterRef, Q, Subquery
from django.http import FileResponse
from django.utils import timezone
from rest_framework.permissions import BasePermission
from rest_framework.views import APIView

from core.responses import error_response, success_response

from apps.candidates.models import Candidate
from apps.pipeline.models import JobApplication

from .models import CandidateReport
from .permissions import CanViewReports
from .report_ai import build_payload
from .pdf_builder import render_candidate_report


def _is_manager_or_admin(user):
    return (
        user.is_superuser
        or getattr(user, "role", "") == "ADMIN"
        or "MANAGER" in (user.role or "").upper()
        or user.has_perm("reports.view_reports")
    )


class CanViewCandidateReports(BasePermission):
    """Admins and managers fully; recruiters read-only (scoped in the queryset)."""

    message = "You do not have permission to view candidate reports."

    def has_permission(self, request, view):
        user = request.user
        if not (user and user.is_authenticated):
            return False
        if _is_manager_or_admin(user):
            return True
        return (user.role or "").upper() == "RECRUITER" or user.has_perm("reports.view_candidatereport")


def visible_candidates(user):
    """Recruiters only see candidates in pipelines of JDs assigned to them."""
    qs = Candidate.objects.all()
    if _is_manager_or_admin(user):
        return qs
    return qs.filter(applications__job__assigned_recruiters__id=user.id).distinct()


def _annotated(user):
    """Visible candidates annotated with their latest report's key fields."""
    latest = CandidateReport.objects.filter(candidate=OuterRef("pk")).order_by("-report_version")
    return visible_candidates(user).annotate(
        rep_id=Subquery(latest.values("id")[:1]),
        rep_version=Subquery(latest.values("report_version")[:1]),
        rep_classification=Subquery(latest.values("classification")[:1]),
        rep_score=Subquery(latest.values("overall_score")[:1]),
        rep_generated=Subquery(latest.values("generated_at")[:1]),
        rep_recommendation=Subquery(latest.values("recommendation_summary")[:1]),
        rep_generated_by=Subquery(latest.values("generated_by__full_name")[:1]),
        rep_generated_by_email=Subquery(latest.values("generated_by__email")[:1]),
    )


def _latest_application(candidate):
    return (
        candidate.applications.select_related("job", "job__client", "stage", "created_by")
        .order_by("-created_at")
        .first()
    )


SORT_FIELDS = {
    "name": "first_name",
    "score": "rep_score",
    "generated": "rep_generated",
    "created": "created_at",
    "status": "status",
}


class CandidateReportListView(APIView):
    """GET /reports/candidate-reports/ — paginated, filterable listing."""

    permission_classes = [CanViewCandidateReports]

    def get(self, request):
        p = request.query_params
        qs = _annotated(request.user)

        if p.get("date_from"):
            qs = qs.filter(created_at__date__gte=p["date_from"])
        if p.get("date_to"):
            qs = qs.filter(created_at__date__lte=p["date_to"])
        if p.get("status"):
            qs = qs.filter(status=p["status"])
        if p.get("classification"):
            qs = qs.filter(rep_classification=p["classification"])
        if p.get("client"):
            qs = qs.filter(applications__job__client_id=p["client"]).distinct()
        if p.get("job"):
            qs = qs.filter(applications__job_id=p["job"]).distinct()
        if p.get("recruiter"):
            qs = qs.filter(applications__created_by_id=p["recruiter"]).distinct()
        if p.get("search"):
            s = p["search"].strip()
            qs = qs.filter(
                Q(first_name__icontains=s) | Q(last_name__icontains=s) | Q(email__icontains=s)
            )

        sort = SORT_FIELDS.get(p.get("sort") or "created", "created_at")
        if (p.get("order") or "desc") == "desc":
            sort = f"-{sort}"
        qs = qs.order_by(sort, "-id")

        try:
            page = max(1, int(p.get("page") or 1))
            # Cap raised to 2000 so the client-side DataTable can load the full
            # set in one request (search/sort/paginate/export happen in-browser).
            page_size = min(2000, max(1, int(p.get("page_size") or 10)))
        except ValueError:
            page, page_size = 1, 10
        total = qs.count()
        candidates = list(qs[(page - 1) * page_size: page * page_size])

        # Latest application per candidate on this page, in one query
        apps_by_candidate = {}
        for app in (
            JobApplication.objects.filter(candidate_id__in=[c.id for c in candidates])
            .select_related("job", "job__client", "stage", "created_by")
            .order_by("candidate_id", "-created_at")
        ):
            apps_by_candidate.setdefault(app.candidate_id, app)

        results = []
        for c in candidates:
            app = apps_by_candidate.get(c.id)
            recruiter = app.created_by if app and app.created_by else c.created_by
            results.append({
                "candidate_id": c.id,
                "candidate_code": f"CAND-{c.id:05d}",
                "name": f"{c.first_name} {c.last_name}".strip(),
                "email": c.email or "",
                "phone": c.phone_number or "",
                "job_title": app.job.title if app else "",
                "client": app.job.client.name if app and app.job.client else "",
                "recruiter": (recruiter.full_name or recruiter.email.split("@")[0]) if recruiter else "",
                "stage": app.stage.name if app and app.stage else "",
                "candidate_status": c.status,
                "classification": c.rep_classification or "",
                "recommendation": c.rep_recommendation or "",
                "generated_by": c.rep_generated_by
                or (c.rep_generated_by_email.split("@")[0] if c.rep_generated_by_email else ""),
                "overall_score": c.rep_score,
                "report_version": c.rep_version,
                "report_generated_at": c.rep_generated.strftime("%d/%m/%Y %H:%M") if c.rep_generated else "",
                "has_report": c.rep_id is not None,
            })

        return success_response({
            "results": results, "total": total, "page": page, "page_size": page_size,
        })


class CandidateReportSummaryView(APIView):
    """GET /reports/candidate-reports/summary/ — the analytics cards."""

    permission_classes = [CanViewCandidateReports]

    def get(self, request):
        candidates = visible_candidates(request.user)
        reports = CandidateReport.objects.filter(candidate__in=candidates)
        today = timezone.localdate()

        latest = _annotated(request.user).filter(rep_id__isnull=False)
        avg_score = latest.aggregate(v=Avg("rep_score"))["v"]

        return success_response({
            "total_reports": reports.count(),
            "generated_today": reports.filter(generated_at__date=today).count(),
            "highly_recommended": latest.filter(
                rep_classification=CandidateReport.Classification.HIGHLY_RECOMMENDED).count(),
            "recommended": latest.filter(
                rep_classification=CandidateReport.Classification.RECOMMENDED).count(),
            "average_score": round(avg_score, 1) if avg_score is not None else 0,
        })


def _get_candidate_or_none(request, candidate_id):
    return visible_candidates(request.user).filter(pk=candidate_id).first()


def _report_dict(report):
    return {
        "id": report.id,
        "version": report.report_version,
        "classification": report.classification,
        "overall_score": report.overall_score,
        "recommendation_summary": report.recommendation_summary,
        "generated_at": report.generated_at.strftime("%d/%m/%Y %H:%M"),
        "generated_by": (
            report.generated_by.full_name or report.generated_by.email.split("@")[0]
        ) if report.generated_by else "",
        "pdf_url": report.pdf_file.url if report.pdf_file else "",
        "payload": report.payload,
    }


class CandidateReportDetailView(APIView):
    """GET /reports/candidate-reports/<id>/ — latest (or ?version=N) report + versions."""

    permission_classes = [CanViewCandidateReports]

    def get(self, request, candidate_id):
        candidate = _get_candidate_or_none(request, candidate_id)
        if not candidate:
            return error_response("Candidate not found.", status_code=404)

        reports = candidate.reports.order_by("-report_version")
        report = reports.first()
        if request.query_params.get("version"):
            report = reports.filter(report_version=request.query_params["version"]).first()

        app = _latest_application(candidate)
        return success_response({
            "candidate": {
                "id": candidate.id,
                "code": f"CAND-{candidate.id:05d}",
                "name": f"{candidate.first_name} {candidate.last_name}".strip(),
                "email": candidate.email or "",
                "job_title": app.job.title if app else "",
                "client": app.job.client.name if app and app.job.client else "",
                "stage": app.stage.name if app and app.stage else "",
            },
            "report": _report_dict(report) if report else None,
            "versions": [
                {"version": r.report_version, "generated_at": r.generated_at.strftime("%d/%m/%Y %H:%M")}
                for r in reports
            ],
        })


class CandidateReportGenerateView(APIView):
    """POST /reports/candidate-reports/<id>/generate/ (and .../regenerate/).

    Each call snapshots the current data into a NEW report version and
    renders its PDF. ADMIN/manager only."""

    permission_classes = [CanViewReports]

    def post(self, request, candidate_id):
        candidate = Candidate.objects.filter(pk=candidate_id).first()
        if not candidate:
            return error_response("Candidate not found.", status_code=404)

        application = _latest_application(candidate)
        payload = build_payload(candidate, application, request.user)
        last = candidate.reports.order_by("-report_version").first()
        version = (last.report_version if last else 0) + 1
        pdf_bytes = render_candidate_report(payload, version=version)

        report = CandidateReport(
            candidate=candidate,
            application=application,
            generated_by=request.user,
            generated_at=timezone.now(),
            report_version=version,
            classification=payload["classification"]["value"],
            overall_score=payload["scores"]["overall"],
            recommendation_summary=payload["recommendation"]["summary"],
            payload=payload,
        )
        report.pdf_file.save(f"candidate_{candidate.id}_report_v{version}.pdf", ContentFile(pdf_bytes), save=True)

        return success_response(_report_dict(report), message=f"Report v{version} generated")


class CandidateReportDownloadView(APIView):
    """GET /reports/candidate-reports/<id>/download/?version=N — the PDF file."""

    permission_classes = [CanViewCandidateReports]

    def perform_content_negotiation(self, request, force=False):
        return super().perform_content_negotiation(request, force=True)

    def get(self, request, candidate_id):
        candidate = _get_candidate_or_none(request, candidate_id)
        if not candidate:
            return error_response("Candidate not found.", status_code=404)

        reports = candidate.reports.order_by("-report_version")
        report = reports.first()
        if request.query_params.get("version"):
            report = reports.filter(report_version=request.query_params["version"]).first()
        if not report or not report.pdf_file:
            return error_response("No generated report for this candidate yet.", status_code=404)

        name = f"{(candidate.first_name or 'candidate')}_{candidate.last_name or ''}".strip("_")
        return FileResponse(
            report.pdf_file.open("rb"),
            as_attachment=True,
            filename=f"{name}_evaluation_v{report.report_version}.pdf",
            content_type="application/pdf",
        )
