"""FastAPI: рекомендации размеров и admin-контур обучения."""

from __future__ import annotations

from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException, Depends, Header, Query
from pydantic import BaseModel
from sqlalchemy.orm import Session
from database import get_db
from model_service import get_model_service
from logging_config import setup_logging, ensure_access_filter
import os
import logging

setup_logging()
logger = logging.getLogger(__name__)

API_KEY = os.getenv("API_KEY")
TRAIN_API_KEY = os.getenv("TRAIN_API_KEY")

if not API_KEY:
    logger.error("API_KEY not set in environment")
    raise ValueError("API_KEY must be set in environment")
if not TRAIN_API_KEY:
    logger.error("TRAIN_API_KEY not set in environment")
    raise ValueError("TRAIN_API_KEY must be set in environment")


@asynccontextmanager
async def lifespan(_app: FastAPI):
    ensure_access_filter()
    yield


app = FastAPI(
    title="API подбора размеров экипировки",
    description=(
        "Публичный контур: /recommend, /model-status (X-API-Key). "
        "Admin/train: /admin/* (X-Train-Api-Key). "
        "Недельный cron Laravel вызывает POST /admin/train и опционально поллит /admin/train/status; "
        "статусы также пушатся в 3PL webhook."
    ),
    version="1.2.0",
    lifespan=lifespan,
)


class RecommendationRequest(BaseModel):
    articles: list[str]
    height: float | None = None
    weight: float | None = None
    shoe_size: float | None = None

    class Config:
        schema_extra = {
            "example": {
                "articles": ["1234", "5678"],
                "height": 180.0,
                "weight": 75.0,
                "shoe_size": 42.0,
            }
        }


def verify_api_key(x_api_key: str = Header(None, alias="X-API-Key")):
    """Проверяет ключ публичного API рекомендаций."""
    if x_api_key != API_KEY:
        logger.error("Invalid API key")
        raise HTTPException(status_code=401, detail="Invalid API key")
    return x_api_key


def verify_train_api_key(x_train_api_key: str = Header(None, alias="X-Train-Api-Key")):
    """Проверяет ключ admin/train контура."""
    if x_train_api_key != TRAIN_API_KEY:
        logger.error("Invalid train API key")
        raise HTTPException(status_code=401, detail="Invalid train API key")
    return x_train_api_key


@app.post("/admin/train", include_in_schema=False)
async def admin_train(
    force: bool = Query(False, description="Активировать даже при провале gate"),
    db: Session = Depends(get_db),
    _train_key: str = Depends(verify_train_api_key),
):
    """
    Запускает асинхронное обучение модели.

    Для недельного cron Laravel: один вызов в неделю; при status=running повторно не стартовать.
    """
    service = get_model_service(db)
    return service.start_training(force=force)


@app.post("/admin/train/cancel", include_in_schema=False)
async def admin_train_cancel(
    db: Session = Depends(get_db),
    _train_key: str = Depends(verify_train_api_key),
):
    """
    Отменяет текущее обучение или сбрасывает зависший статус running.

    Если поток обучения жив — кооперативная остановка между этапами.
    Если статус running, а потока нет (рестарт пода) — принудительный сброс.
    """
    service = get_model_service(db)
    return service.cancel_training()


@app.post("/admin/train/restart", include_in_schema=False)
async def admin_train_restart(
    force: bool = Query(False, description="Активировать даже при провале gate"),
    db: Session = Depends(get_db),
    _train_key: str = Depends(verify_train_api_key),
):
    """
    Отменяет текущий/зависший прогон и запускает обучение заново.

    Если поток ещё останавливается — новый старт выполнится после выхода.
    """
    service = get_model_service(db)
    return service.restart_training(force=force)


@app.get("/admin/train/status", include_in_schema=False)
async def admin_train_status(
    db: Session = Depends(get_db),
    _train_key: str = Depends(verify_train_api_key),
):
    """Полный персистентный статус последнего/текущего обучения."""
    service = get_model_service(db)
    return service.get_progress()


@app.get("/admin/models/report", include_in_schema=False)
async def admin_models_report(
    db: Session = Depends(get_db),
    _train_key: str = Depends(verify_train_api_key),
):
    """Человекочитаемая сводка по всем версиям для сравнения качества."""
    service = get_model_service(db)
    return service.get_models_report()


@app.get("/admin/models/compare", include_in_schema=False)
async def admin_models_compare(
    a: int = Query(..., description="Первая версия"),
    b: int = Query(..., description="Вторая версия"),
    db: Session = Depends(get_db),
    _train_key: str = Depends(verify_train_api_key),
):
    """Сравнение двух версий модели."""
    service = get_model_service(db)
    try:
        return service.compare_model_versions(a, b)
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e)) from e


@app.get("/admin/models/{version}/report", include_in_schema=False)
async def admin_model_version_report(
    version: int,
    db: Session = Depends(get_db),
    _train_key: str = Depends(verify_train_api_key),
):
    """Подробный human-readable отчёт одной версии."""
    service = get_model_service(db)
    try:
        return service.get_version_report(version)
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e)) from e


@app.post("/admin/models/{version}/activate", include_in_schema=False)
async def admin_activate_model(
    version: int,
    db: Session = Depends(get_db),
    _train_key: str = Depends(verify_train_api_key),
):
    """Ручная активация / откат версии модели."""
    service = get_model_service(db)
    try:
        return service.switch_version(version)
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e)) from e


@app.delete("/admin/models/{version}", include_in_schema=False)
async def admin_delete_model(
    version: int,
    db: Session = Depends(get_db),
    _train_key: str = Depends(verify_train_api_key),
):
    """Удаление версии модели."""
    service = get_model_service(db)
    try:
        return service.delete_version(version)
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e)) from e


@app.post("/train", include_in_schema=False)
async def train_deprecated(
    force: bool = Query(False),
    db: Session = Depends(get_db),
    _train_key: str = Depends(verify_train_api_key),
):
    """Deprecated alias → /admin/train (только TRAIN_API_KEY)."""
    service = get_model_service(db)
    return service.start_training(force=force)


@app.get("/train-progress", include_in_schema=False)
async def train_progress_deprecated(
    db: Session = Depends(get_db),
    _train_key: str = Depends(verify_train_api_key),
):
    """Deprecated alias → /admin/train/status."""
    service = get_model_service(db)
    return service.get_progress()


@app.post(
    "/recommend",
    summary="Получить рекомендации по размерам экипировки",
    description=(
        "Возвращает рекомендации по размерам для указанных артикулов. "
        "В ответе: sizes, probability, sku, confident, при ошибках — code/message. "
        "Требуется заголовок X-API-Key."
    ),
    response_description="Список рекомендаций по размерам для каждого артикула с вероятностями.",
)
async def recommend(
    request: RecommendationRequest,
    db: Session = Depends(get_db),
    _api_key: str = Depends(verify_api_key),
):
    """
    Параметры:
    - articles: Список артикулов (строки, начинающиеся с 4 цифр).
    - height / weight / shoe_size: опционально.

    Пример ответа:
    ```json
    [
      {
        "article": "1234",
        "sizes": [{"size": "M", "probability": 80, "sku": "1234-M"}],
        "confident": true,
        "confidence_gap": 60
      }
    ]
    ```
    """
    service = get_model_service(db)
    return service.predict(request.articles, request.height, request.weight, request.shoe_size)


@app.get(
    "/model-status",
    summary="Получить статус модели",
    description="Текущая версия, список версий и краткие human-метрики. Требуется X-API-Key.",
)
async def get_model_status(
    db: Session = Depends(get_db),
    _api_key: str = Depends(verify_api_key),
):
    service = get_model_service(db)
    return service.get_status()


@app.get("/health", summary="Проверка состояния сервиса")
async def health():
    return {"status": "ok"}
