"""Builds the evaluation payload for a candidate PDF report.

The environment is offline (no LLM access), so the "AI" sections are
deterministic heuristics composed from real data: the candidate profile,
their pipeline application (score, stage, interview fields) and the JD's
skill requirements. Regenerating without data changes yields the same
payload, so report versions only differ when something actually changed.
"""

import os
import re

from django.utils import timezone

from apps.pipeline.models import PipelineStage


def _clamp(v, lo=0, hi=100):
    return max(lo, min(hi, round(v)))


def _skill_names(candidate):
    return [s.name for s in candidate.skills.all()]


def _jd_skills(job):
    if not job:
        return []
    raw = f"{job.must_have_skills or ''},{job.good_to_have_skills or ''}"
    return [s.strip() for s in re.split(r"[,\n;]+", raw) if s.strip()]


def _skill_overlap_pct(candidate_skills, jd_skills):
    if not jd_skills:
        return None
    cand = {s.lower() for s in candidate_skills}
    matched = [s for s in jd_skills if s.lower() in cand]
    return _clamp(len(matched) / len(jd_skills) * 100), matched


def _required_years(job):
    """Parse the first number out of an experience band like '3-5 years'."""
    if not job or not job.experience_band:
        return None
    m = re.search(r"\d+(?:\.\d+)?", job.experience_band)
    return float(m.group()) if m else None


def build_scores(candidate, application):
    """Six 0-100 sub-scores derived from the application score + profile signals."""
    job = application.job if application else None
    base = application.score if application and application.score is not None else None
    skills = _skill_names(candidate)

    # Data-completeness fallback when no screening score exists yet
    if base is None:
        signals = [
            bool(candidate.professional_summary), bool(skills), bool(candidate.resume),
            bool(candidate.highest_qualification), candidate.total_experience is not None,
        ]
        base = 40 + sum(signals) * 6

    communication = base + (6 if candidate.professional_summary else -4) + (3 if candidate.linkedin else 0)
    technical = base + (6 if len(skills) >= 5 else 2 if len(skills) >= 3 else -5)
    problem_solving = base + (4 if candidate.projects.exists() else -3)

    exp_years = float(candidate.total_experience or 0)
    required = _required_years(job)
    if required is None:
        experience_match = base
    elif exp_years >= required:
        experience_match = base + 8
    elif exp_years >= required * 0.6:
        experience_match = base
    else:
        experience_match = base - 12

    overlap = _skill_overlap_pct(skills, _jd_skills(job))
    if overlap is None:
        skill_match, matched_skills = base, []
    else:
        skill_match, matched_skills = overlap[0], overlap[1]
        # blend with base so a thin JD skill list doesn't dominate
        skill_match = round(0.6 * skill_match + 0.4 * base)

    scores = {
        "communication": _clamp(communication),
        "technical": _clamp(technical),
        "problem_solving": _clamp(problem_solving),
        "experience_match": _clamp(experience_match),
        "skill_match": _clamp(skill_match),
    }
    scores["overall"] = _clamp(
        scores["communication"] * 0.15 + scores["technical"] * 0.25
        + scores["problem_solving"] * 0.20 + scores["experience_match"] * 0.20
        + scores["skill_match"] * 0.20
    )
    return scores, matched_skills


def classify(overall):
    if overall >= 80:
        return "Highly Recommended"
    if overall >= 65:
        return "Recommended"
    if overall >= 45:
        return "Consider"
    return "Not Recommended"


def _confidence(candidate, application):
    """How much data backed this evaluation (drives the confidence figure)."""
    signals = [
        application is not None,
        application is not None and application.score is not None,
        bool(candidate.professional_summary),
        candidate.skills.exists(),
        bool(candidate.resume),
        candidate.total_experience is not None,
        bool(candidate.highest_qualification) or candidate.educations.exists(),
        application is not None and bool(application.interview_result),
    ]
    return _clamp(35 + sum(signals) * 8)


def _next_stage(application):
    if not application or not application.stage:
        return "Screening"
    stages = list(
        PipelineStage.objects.filter(is_active=True)
        .exclude(outcome=PipelineStage.Outcome.LOST)
        .order_by("sort_order", "id")
    )
    ids = [s.id for s in stages]
    if application.stage_id in ids:
        idx = ids.index(application.stage_id)
        if idx + 1 < len(stages):
            return stages[idx + 1].name
        return "Onboarding"
    return stages[0].name if stages else "Screening"


def build_payload(candidate, application, generated_by):
    """The full report content, snapshotted into CandidateReport.payload."""
    job = application.job if application else None
    skills = _skill_names(candidate)
    scores, matched_skills = build_scores(candidate, application)
    overall = scores["overall"]
    classification = classify(overall)
    jd_skills = _jd_skills(job)
    gaps = [s for s in jd_skills if s.lower() not in {x.lower() for x in skills}][:8]

    strengths = []
    if scores["technical"] >= 65 and skills:
        strengths.append(f"Solid technical footprint across {', '.join(skills[:4])}")
    if matched_skills:
        strengths.append(f"Direct match on required skills: {', '.join(matched_skills[:4])}")
    if scores["communication"] >= 65:
        strengths.append("Communicates their profile and experience clearly")
    if float(candidate.total_experience or 0) > 0:
        strengths.append(f"{candidate.total_experience} years of relevant industry experience")
    if candidate.projects.exists():
        strengths.append("Demonstrated hands-on project delivery")
    if not strengths:
        strengths.append("Early-stage profile — potential to grow into the role")

    weaknesses = []
    if scores["skill_match"] < 60 and gaps:
        weaknesses.append(f"Missing some required skills: {', '.join(gaps[:3])}")
    if scores["experience_match"] < 55:
        weaknesses.append("Experience level below the JD's stated band")
    if not candidate.professional_summary:
        weaknesses.append("Profile summary not provided — harder to assess fit")
    if not candidate.resume:
        weaknesses.append("No resume on file")
    if not weaknesses:
        weaknesses.append("No significant gaps identified from available data")

    interview_areas = (gaps[:3] or skills[:3] or ["Role fundamentals"])
    next_stage = _next_stage(application)

    recommendation = (
        f"{classification} for the {job.title if job else 'open'} position. "
        f"Overall evaluation score {overall}/100 with {scores['skill_match']}% skill alignment. "
        f"Suggested next step: proceed to {next_stage}."
        if classification != "Not Recommended"
        else f"Not recommended for the {job.title if job else 'open'} position at this time "
        f"(overall score {overall}/100). Consider for roles with a closer skill and experience fit."
    )

    education = [
        {
            "degree": e.degree_name,
            "field": e.field_of_study or "",
            "institution": e.institution_name,
            "year": e.passing_year,
        }
        for e in candidate.educations.all()
    ]
    if not education and candidate.highest_qualification:
        education = [{
            "degree": candidate.highest_qualification,
            "field": "",
            "institution": candidate.university or candidate.college or "",
            "year": candidate.passing_year,
        }]

    interview = None
    if application and (application.interviewer or application.interview_date or application.interview_result):
        interview = {
            "interviewer": application.interviewer or "",
            "date": application.interview_date.isoformat() if application.interview_date else "",
            "result": application.interview_result or "",
            "notes": application.remark or "",
            "transcript": None,  # transcripts are not captured by the ATS yet
            "duration": "",
            "language": "English",
        }

    return {
        "candidate": {
            "id": candidate.id,
            "name": f"{candidate.first_name} {candidate.last_name}".strip(),
            "email": candidate.email or "",
            "phone": candidate.phone_number or "",
            "location": candidate.current_location or candidate.city or "",
            "experience_years": float(candidate.total_experience or 0),
            "current_company": candidate.current_company or "",
            "current_role": candidate.current_role or "",
            "skills": skills,
            "education": education,
            "resume_file": os.path.basename(candidate.resume.name) if candidate.resume else "",
            "resume_uploaded": candidate.updated_at.strftime("%d/%m/%Y") if candidate.resume else "",
            "status": candidate.status,
        },
        "job": {
            "id": job.id if job else None,
            "title": job.title if job else "",
            "client": job.client.name if job and job.client else "",
            "stage": application.stage.name if application and application.stage else "",
        },
        "summary": {
            "professional_summary": candidate.professional_summary
                or f"{'Experienced' if float(candidate.total_experience or 0) >= 2 else 'Early-career'} "
                   f"candidate{' currently at ' + candidate.current_company if candidate.current_company else ''}"
                   f"{' working as ' + candidate.current_role if candidate.current_role else ''}, "
                   f"with {len(skills)} recorded skill{'s' if len(skills) != 1 else ''}"
                   f"{' and ' + str(candidate.total_experience) + ' years of experience' if candidate.total_experience else ''}.",
            "key_strengths": strengths[:4],
            "technologies": skills[:10],
            "domain_experience": candidate.current_company or "Not specified",
            "career_highlights": [
                f"{e.role} at {e.company_name}" for e in candidate.experiences.all()[:4]
            ] or ([f"{candidate.current_role} at {candidate.current_company}"]
                  if candidate.current_role and candidate.current_company else []),
        },
        "interview": interview,
        "scores": scores,
        "classification": {
            "value": classification,
            "confidence": _confidence(candidate, application),
            "matching_percentage": scores["skill_match"],
        },
        "recommendation": {
            "summary": recommendation,
            "strengths": strengths[:5],
            "weaknesses": weaknesses[:4],
            "skill_gaps": gaps,
            "interview_areas": interview_areas,
            "next_stage": next_stage,
            "notes": f"Evaluation generated from profile data, pipeline stage and screening score"
                     f"{' by ' + (generated_by.full_name or generated_by.email) if generated_by else ''}.",
        },
        "meta": {
            "generated_at": timezone.now().strftime("%d/%m/%Y %H:%M"),
            "system_version": "TA-ATS 1.0",
        },
    }
