from fastapi import FastAPI, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
from contextlib import asynccontextmanager

from schemas import TransactionInput, PredictionResponse, HealthResponse
from model_loader import load_models, get_available_models, predict


# ── Lifespan (replaces deprecated on_event startup/shutdown) ──────────────────
@asynccontextmanager
async def lifespan(app: FastAPI):
    """Load models once when the server starts; release on shutdown."""
    print("[startup] Loading models...")
    load_models()
    print(f"[startup] Ready. Models available: {get_available_models()}")
    yield
    print("[shutdown] FraudShield API shutting down.")


# ── App ───────────────────────────────────────────────────────────────────────
app = FastAPI(
    title="FraudShield API",
    description=(
        "AI-Driven Fraud & Anomaly Detection System. "
        "Submit a credit card transaction and receive an instant fraud risk prediction."
    ),
    version="1.0.0",
    lifespan=lifespan,
)

# ── CORS (allows the HTML/JS frontend to call the API) ───────────────────────
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],   # Tighten this to your frontend URL in production
    allow_methods=["*"],
    allow_headers=["*"],
)


# ── Routes ────────────────────────────────────────────────────────────────────

@app.get("/", tags=["General"])
def root():
    """Welcome message and quick-start info."""
    return {
        "message": "Welcome to FraudShield API 🛡️",
        "docs":    "/docs",
        "health":  "/health",
    }


@app.get("/health", response_model=HealthResponse, tags=["General"])
def health_check():
    """
    Health check endpoint.
    Returns 200 with the list of loaded models if the service is healthy.
    """
    loaded = get_available_models()
    if not loaded:
        raise HTTPException(status_code=503, detail="No models are loaded.")
    return HealthResponse(status="ok", models_loaded=loaded)


@app.post("/predict", response_model=PredictionResponse, tags=["Prediction"])
def predict_fraud(
    transaction: TransactionInput,
    model: str = Query(
        default="random_forest",
        description="Model to use for prediction. Options: logistic_regression, random_forest, isolation_forest",
    ),
):
    """
    Predict whether a credit card transaction is fraudulent.

    - **V1–V28**: PCA-transformed features (provided as-is from the dataset)
    - **Time**: Raw seconds elapsed since the first transaction in the dataset
    - **Amount**: Raw transaction amount in USD

    The API scales `Time` and `Amount` internally before inference.
    """
    available = get_available_models()
    if model not in available:
        raise HTTPException(
            status_code=400,
            detail=f"Model '{model}' is not available. Choose from: {available}",
        )

    try:
        result = predict(model_name=model, transaction_dict=transaction.model_dump())
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Prediction error: {str(e)}")

    return PredictionResponse(model_used=model, **result)


@app.get("/models", tags=["General"])
def list_models():
    """List all currently loaded models."""
    return {"available_models": get_available_models()}