from pathlib import Path
import tempfile

from fastapi import FastAPI, File, Form, HTTPException, UploadFile
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field

from audio_utils_api import SUPPORTED_AUDIO_EXTENSIONS, cleanup_temp_files
from database import (
    authenticate_user,
    create_user,
    get_database_label,
    init_db,
    list_prediction_results,
    save_prediction_result,
)
from predict_api import MODEL_NOT_FOUND_MESSAGE, get_model_info, predict_audio


app = FastAPI(title="ConfiVoice Prediction API")

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


class PredictionSaveRequest(BaseModel):
    user_id: int | None = None
    student_name: str
    student_gender: str | None = None
    predicted_label: str | None = None
    label: str | None = None
    description: str | None = None
    confidence: float = 0
    probability_pd: float = 0
    probability_tpd: float = 0
    is_valid_audio: bool = True
    error_message: str | None = None
    audio_quality: dict = Field(default_factory=dict)
    voice_indicators: dict = Field(default_factory=dict)


class AuthRequest(BaseModel):
    username: str
    password: str


class RegisterRequest(AuthRequest):
    full_name: str


@app.on_event("startup")
def startup():
    init_db()


@app.get("/")
def read_root():
    return {
        "name": "ConfiVoice Prediction API",
        "status": "ok",
        "predict_endpoint": "/predict",
        "predictions_endpoint": "/predictions",
        "database": get_database_label(),
    }


@app.get("/model")
def read_model_info():
    try:
        return get_model_info()
    except FileNotFoundError as error:
        raise HTTPException(status_code=404, detail=MODEL_NOT_FOUND_MESSAGE) from error
    except Exception as error:
        raise HTTPException(status_code=500, detail=str(error)) from error


@app.get("/predictions")
def read_predictions(limit: int = 200, user_id: int | None = None):
    return {
        "database": get_database_label(),
        "data": list_prediction_results(limit=limit, user_id=user_id),
    }


@app.post("/register")
def register(payload: RegisterRequest):
    full_name = " ".join(payload.full_name.strip().split())
    username = payload.username.strip().lower()
    password = payload.password

    if not full_name:
        raise HTTPException(status_code=400, detail="Nama lengkap wajib diisi.")
    if len(username) < 3:
        raise HTTPException(status_code=400, detail="Username minimal 3 karakter.")
    if len(password) < 6:
        raise HTTPException(status_code=400, detail="Password minimal 6 karakter.")

    try:
        user = create_user(full_name, username, password)
    except Exception as error:
        message = str(error).lower()
        if "duplicate" in message or "unique" in message:
            raise HTTPException(status_code=409, detail="Username sudah terdaftar.") from error
        raise HTTPException(status_code=500, detail=str(error)) from error

    return {"status": "registered", "user": user}


@app.post("/login")
def login(payload: AuthRequest):
    username = payload.username.strip().lower()
    user = authenticate_user(username, payload.password)
    if not user:
        raise HTTPException(status_code=401, detail="Username atau password salah.")
    return {"status": "logged_in", "user": user}


@app.post("/predictions")
def create_prediction(payload: PredictionSaveRequest):
    student_name = payload.student_name.strip()
    if not student_name:
        raise HTTPException(status_code=400, detail="Nama siswa/i wajib diisi.")

    result = payload.model_dump()
    prediction_id = save_prediction_result(student_name, result)
    return {
        "status": "saved",
        "prediction_id": prediction_id,
        "student_name": student_name,
    }


@app.post("/save")
def save_prediction(payload: PredictionSaveRequest):
    return create_prediction(payload)


@app.post("/predict")
async def predict(
    file: UploadFile = File(...),
    student_name: str = Form("Tanpa Nama"),
    student_gender: str = Form(""),
):
    suffix = Path(file.filename or "").suffix.lower()
    if suffix not in SUPPORTED_AUDIO_EXTENSIONS:
        allowed = ", ".join(sorted(SUPPORTED_AUDIO_EXTENSIONS))
        raise HTTPException(
            status_code=400,
            detail=f"Format audio tidak didukung. Format yang didukung: {allowed}",
        )

    temp_input = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
    temp_input_path = Path(temp_input.name)

    try:
        content = await file.read()
        temp_input.write(content)
        temp_input.close()

        if not content:
            raise HTTPException(status_code=400, detail="File audio kosong.")

        clean_student_name = student_name.strip() or "Tanpa Nama"
        result = predict_audio(temp_input_path)
        result["student_name"] = clean_student_name
        result["student_gender"] = student_gender.strip() or None
        return result
    except HTTPException:
        raise
    except FileNotFoundError as error:
        raise HTTPException(status_code=404, detail=MODEL_NOT_FOUND_MESSAGE) from error
    except Exception as error:
        raise HTTPException(status_code=500, detail=str(error)) from error
    finally:
        temp_input.close()
        cleanup_temp_files(temp_input_path)
