from functools import lru_cache
from pathlib import Path
from typing import Any

import joblib
import numpy as np
from fastapi import APIRouter, Depends, HTTPException

from ..core.config import get_settings
from ..db_models.internal import UsuarioAdmin
from ..dependencies import get_current_admin
from ..schemas.inference import (
    AvgFinalInput, AvgFinalOutput,
    GradeTendencyInput, GradeTendencyOutput,
    MathFinalInput, MathFinalOutput,
    MathPassInput, MathPassOutput,
    SupportLevelInput, SupportLevelOutput,
)

router = APIRouter(prefix="/inference", tags=["Inferencia de Modelos"])
settings = get_settings()

ENCODING_INFO = {
    "gender_encoded": {"F": 0, "M": 1},
    "course_encoded": {
        "Primary 6": 0, "Secondary 1": 1, "Secondary 2": 2,
        "Secondary 3": 3, "Secondary 4": 4, "Secondary 5": 5, "Secondary 6": 6,
    },
    "support_level_num": {"Low": 0, "Medium": 1, "High": 2},
    "grade_tendency_encoded": {"Downtrend": 0, "Stable": 0, "Uptrend": 1},
}


@lru_cache(maxsize=1)
def _load_models() -> dict[str, Any]:
    pkl_dir = Path(settings.models_base_path) / "models-pkl"
    return {
        "linear_avg_final": joblib.load(pkl_dir / "linear_avg_final.pkl"),
        "linear_math_final": joblib.load(pkl_dir / "linear_mathematics_final.pkl"),
        "logistic_grade_tendency": joblib.load(pkl_dir / "logistic_grade_tendency.pkl"),
        "logistic_math_pass": joblib.load(pkl_dir / "logistic_mathematics_pass.pkl"),
        "logistic_support_level": joblib.load(pkl_dir / "logistic_support_level.pkl"),
    }


@router.get("/encodings", summary="Tabla de codificaciones para variables categóricas")
def get_encodings(_: UsuarioAdmin = Depends(get_current_admin)):
    return ENCODING_INFO


@router.post("/avg-final", response_model=AvgFinalOutput, summary="M1 — Predice el promedio final del estudiante")
def predict_avg_final(body: AvgFinalInput, _: UsuarioAdmin = Depends(get_current_admin)):
    model = _load_models()["linear_avg_final"]
    X = np.array([[
        body.age, body.gender_encoded, body.course_encoded,
        body.attendance_absences_total, body.support_level_num,
        body.total_subjects, body.term1_avg,
    ]])
    pred = float(model.predict(X)[0])
    return AvgFinalOutput(predicted_avg_final=round(pred, 4))


@router.post("/math-final", response_model=MathFinalOutput, summary="M4 — Predice la nota final de Matemáticas")
def predict_math_final(body: MathFinalInput, _: UsuarioAdmin = Depends(get_current_admin)):
    model = _load_models()["linear_math_final"]
    X = np.array([[
        body.Mathematics_term1, body.Mathematics_term2,
        body.Mathematics_term3, body.attendance_absences_total,
    ]])
    pred = float(model.predict(X)[0])
    return MathFinalOutput(predicted_math_final=round(pred, 4))


@router.post("/grade-tendency", response_model=GradeTendencyOutput, summary="M2 — Predice la tendencia de notas")
def predict_grade_tendency(body: GradeTendencyInput, _: UsuarioAdmin = Depends(get_current_admin)):
    scaler, model = _load_models()["logistic_grade_tendency"]
    X = np.array([[
        body.term1_avg, body.term2_avg, body.term3_avg,
        body.attendance_absences_total, body.support_level_num,
        body.age, body.gender_encoded,
    ]])
    X_scaled = scaler.transform(X)
    pred_class = int(model.predict(X_scaled)[0])
    proba = model.predict_proba(X_scaled)[0]
    return GradeTendencyOutput(
        predicted_class=pred_class,
        label="Uptrend" if pred_class == 1 else "Downtrend",
        probability_downtrend=round(float(proba[0]), 4),
        probability_uptrend=round(float(proba[1]), 4),
    )


@router.post("/math-pass", response_model=MathPassOutput, summary="M3 — Predice aprobado/suspenso en Matemáticas")
def predict_math_pass(body: MathPassInput, _: UsuarioAdmin = Depends(get_current_admin)):
    scaler, model = _load_models()["logistic_math_pass"]
    X = np.array([[
        body.Mathematics_term1, body.Mathematics_term2,
        body.Mathematics_term3, body.attendance_absences_total,
        body.support_level_num,
    ]])
    X_scaled = scaler.transform(X)
    pred_class = int(model.predict(X_scaled)[0])
    proba = model.predict_proba(X_scaled)[0]
    return MathPassOutput(
        predicted_class=pred_class,
        label="Pass" if pred_class == 1 else "Fail",
        probability_fail=round(float(proba[0]), 4),
        probability_pass=round(float(proba[1]), 4),
    )


@router.post("/support-level", response_model=SupportLevelOutput, summary="M5 — Predice el nivel de soporte necesario")
def predict_support_level(body: SupportLevelInput, _: UsuarioAdmin = Depends(get_current_admin)):
    scaler, model = _load_models()["logistic_support_level"]
    X = np.array([[
        body.term1_avg, body.attendance_absences_total,
        body.failed_subjects, body.grade_tendency_encoded,
        body.age, body.gender_encoded,
    ]])
    X_scaled = scaler.transform(X)
    pred_class = int(model.predict(X_scaled)[0])
    proba = model.predict_proba(X_scaled)[0]
    labels = {0: "Low", 1: "Medium", 2: "High"}
    return SupportLevelOutput(
        predicted_class=pred_class,
        label=labels[pred_class],
        probability_low=round(float(proba[0]), 4),
        probability_medium=round(float(proba[1]), 4),
        probability_high=round(float(proba[2]), 4),
    )
