"""Candidate-facing apply endpoints (careers portal).

A logged-in CANDIDATE applies to a Published JD and lists their own
applications. Applications are keyed to the candidate profile linked to the
logged-in user (Candidate.user OneToOne).
"""

from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView

from core.responses import success_response, error_response
from apps.jobs.models import JobDescription
from .models import JobApplication, PipelineStage


def _my_candidate(user):
    from apps.candidates.models import Candidate
    return Candidate.get_for_user(user)



def _resolve_application_source(request, job, candidate):
    """Decide the candidate source for this application.

    Order of trust:
      1. `t` — the encrypted tracking token from the public application URL.
         The platform, JD reference and tracking metadata are decrypted
         server-side, so the visitor can neither read nor edit the source.
         A token minted for a different JD is ignored.
      2. `source` — the legacy plain query value, still accepted so links that
         were already distributed as `?source=linkedin` keep working.
      3. Neither → left to the existing Direct handling.

    Returns the resolved Candidate.SourceChoices value, or None when nothing
    was supplied (the candidate's current source is then left untouched, as
    before).
    """
    from apps.jobs.tracking_tokens import record_token_use, resolve_source_for_application

    token = (
        request.data.get("t")
        or request.data.get("tracking_token")
        or request.query_params.get("t")
        or ""
    )
    source, info = resolve_source_for_application(str(token).strip(), job=job)
    if source:
        record_token_use(info)   # best-effort attribution counter
        return source

    legacy = (request.data.get("source") or "").strip().upper()
    if legacy and legacy in candidate.SourceChoices.values:
        return legacy
    return None


class CandidateApplyView(APIView):
    """POST /api/v1/public/apply/  { job_id, t? , source? }  (auth required)

    Applies the logged-in candidate to a Published JD. `t` is the encrypted
    tracking token from the public application URL — the platform source is
    resolved from it server-side (see apps.jobs.tracking_tokens)."""

    permission_classes = [IsAuthenticated]

    def post(self, request):
        candidate = _my_candidate(request.user)
        if not candidate:
            return error_response(
                "No candidate profile found for your account. Please complete your profile first.",
                status_code=400,
            )
        job_id = request.data.get("job_id") or request.data.get("job")
        if not job_id:
            return error_response("job_id is required.", status_code=400)
        # Accept the public UUID or the legacy integer id.
        import uuid
        pub = JobDescription.objects.filter(status="Published")
        try:
            uuid.UUID(str(job_id))
            job = pub.filter(public_id=job_id).first()
        except (ValueError, TypeError):
            job = pub.filter(id=job_id).first() if str(job_id).isdigit() else None
        if not job:
            return error_response("This job is not open for applications.", status_code=404)

        stage = PipelineStage.objects.filter(is_active=True).order_by("sort_order", "id").first()
        app, created = JobApplication.objects.get_or_create(
            candidate=candidate, job=job,
            defaults={"stage": stage, "created_by": request.user},
        )

        source = _resolve_application_source(request, job, candidate)
        if source:
            candidate.source = source
            candidate.save(update_fields=["source"])

        if not created:
            return success_response(
                {"application_id": app.id, "job_id": job.id},
                "You have already applied to this job.",
            )

        # Notify the JD's assigned recruiter(s), and confirm receipt to the
        # candidate. Only on first creation, so a repeated apply can't be used
        # to spam either side. Neither call raises — a mail outage must never
        # fail the candidate's successful application.
        from apps.notifications.services import (
            notify_candidate_of_application,
            notify_recruiters_of_application,
        )
        notify_recruiters_of_application(app)
        notify_candidate_of_application(app)

        return success_response(
            {"application_id": app.id, "job_id": job.id},
            f"Applied to {job.title} successfully.",
            status_code=201,
        )


class MyApplicationsView(APIView):
    """GET /api/v1/public/my-applications/  (auth required)
    The logged-in candidate's own applications with job + stage info."""

    permission_classes = [IsAuthenticated]

    def get(self, request):
        candidate = _my_candidate(request.user)
        if not candidate:
            return success_response([], "No candidate profile yet.")
        apps = (
            JobApplication.objects.filter(candidate=candidate)
            .select_related("job", "stage")
            .order_by("-created_at")
        )
        data = [{
            "application_id": a.id,
            "job_id": a.job_id,
            "job_public_id": str(a.job.public_id) if (a.job and a.job.public_id) else str(a.job_id),
            "job_title": a.job.title if a.job else None,
            "location": a.job.location if a.job else None,
            "stage": a.stage.name if a.stage else None,
            "round1_outcome": a.round1_outcome,
            "applied_at": a.created_at.isoformat(),
        } for a in apps]
        return success_response(data)
