from pathlib import Path

import joblib
import numpy as np

from audio_utils_api import convert_audio_to_wav
from features import (
    LABEL_PD,
    LABEL_TPD,
    analyze_audio,
    build_prediction_explanation,
)


BASE_DIR = Path(__file__).resolve().parent
PROJECT_DIR = BASE_DIR.parent
MODEL_DIR = BASE_DIR / "models"
MODEL_PATH = MODEL_DIR / "svm_voice_confidence_model.joblib"

CONFIDENCE_THRESHOLD = 0.60
MODEL_NOT_FOUND_MESSAGE = "Model tidak ditemukan. Pastikan file model berada di folder ml/models/."

LABEL_DESCRIPTION = {
    LABEL_PD: "Percaya Diri",
    LABEL_TPD: "Tidak Percaya Diri",
}


def find_model_path():
    if MODEL_PATH.exists():
        return MODEL_PATH

    available_models = sorted(MODEL_DIR.glob("*.joblib"))
    if available_models:
        return available_models[0]

    raise FileNotFoundError(MODEL_NOT_FOUND_MESSAGE)


def load_prediction_model(model_path=None):
    model_path = Path(model_path) if model_path else find_model_path()
    if not model_path.exists():
        raise FileNotFoundError(MODEL_NOT_FOUND_MESSAGE)
    return joblib.load(model_path)


def get_expected_feature_count(model):
    if hasattr(model, "named_steps") and "scaler" in model.named_steps:
        return getattr(model.named_steps["scaler"], "n_features_in_", None)
    return getattr(model, "n_features_in_", None)


def calculate_indicator_probability(model_probability_pd, indicators):
    indicator_pd = (
        0.15 * indicators["volume_score"]
        + 0.35 * indicators["intonation_score"]
        + 0.50 * indicators["pause_score"]
    )

    adjusted_pd = 0.80 * model_probability_pd + 0.20 * indicator_pd

    if indicators["pause_score"] < 0.35:
        adjusted_pd -= (0.35 - indicators["pause_score"]) * 0.25
    if indicators["volume_score"] < 0.18:
        adjusted_pd -= (0.18 - indicators["volume_score"]) * 0.10
    if indicators["intonation_score"] < 0.45:
        adjusted_pd -= (0.45 - indicators["intonation_score"]) * 0.15

    return float(np.clip(adjusted_pd, 0.01, 0.99)), float(np.clip(indicator_pd, 0.0, 1.0))


def predict_audio(audio_path, model_path=None):
    model = load_prediction_model(model_path)
    wav_path = convert_audio_to_wav(audio_path)

    try:
        try:
            analysis = analyze_audio(wav_path, validate_quality=True)
        except ValueError as error:
            fallback_analysis = analyze_audio(wav_path, validate_quality=False)
            return {
                "is_valid_audio": False,
                "error_message": str(error),
                "predicted_label": None,
                "description": "Audio tidak valid",
                "confidence": 0.0,
                "probability_pd": 0.0,
                "probability_tpd": 0.0,
                "margin": 0.0,
                "audio_quality": fallback_analysis["audio_quality"],
                "voice_indicators": fallback_analysis["voice_indicators"],
                "explanation": str(error),
            }

        features = analysis["features"]
        feature_matrix = np.asarray(features, dtype=np.float32).reshape(1, -1)

        expected_feature_count = get_expected_feature_count(model)
        if expected_feature_count and feature_matrix.shape[1] != expected_feature_count:
            raise ValueError(
                "Jumlah fitur audio tidak sesuai dengan model. "
                f"Audio menghasilkan {feature_matrix.shape[1]} fitur, "
                f"sedangkan model mengharapkan {expected_feature_count} fitur."
            )

        prediction = model.predict(feature_matrix)
        probabilities = model.predict_proba(feature_matrix)

        predicted_label = str(prediction[0])
        class_probabilities = {
            str(label): float(probability)
            for label, probability in zip(model.classes_, probabilities[0])
        }
        model_probability_pd = float(class_probabilities.get(LABEL_PD, 0.0))
        probability_pd, indicator_pd_score = calculate_indicator_probability(
            model_probability_pd,
            analysis["voice_indicators"],
        )
        probability_tpd = float(1.0 - probability_pd)
        predicted_label = LABEL_PD if probability_pd >= probability_tpd else LABEL_TPD
        confidence = float(max(probability_pd, probability_tpd))
        margin = float(abs(probability_pd - probability_tpd))
        explanation = build_prediction_explanation(
            predicted_label,
            confidence,
            analysis["voice_indicators"],
        )

        return {
            "is_valid_audio": True,
            "predicted_label": predicted_label,
            "label": predicted_label,
            "description": LABEL_DESCRIPTION.get(predicted_label, predicted_label),
            "confidence": confidence,
            "probability_pd": probability_pd,
            "probability_tpd": probability_tpd,
            "margin": margin,
            "probabilities": {
                LABEL_PD: probability_pd,
                LABEL_TPD: probability_tpd,
            },
            "audio_quality": analysis["audio_quality"],
            "voice_indicators": analysis["voice_indicators"],
            "indicator_pd_score": indicator_pd_score,
            "model_probability_pd": model_probability_pd,
            "model_probability_tpd": float(class_probabilities.get(LABEL_TPD, 0.0)),
            "explanation": explanation,
        }
    finally:
        Path(wav_path).unlink(missing_ok=True)


def get_model_info():
    model_path = find_model_path()
    model = load_prediction_model(model_path)

    return {
        "model_path": str(model_path.relative_to(PROJECT_DIR)),
        "model_type": type(model).__name__,
        "classes": [str(label) for label in getattr(model, "classes_", [])],
        "expected_features": get_expected_feature_count(model),
    }
