"""
AI Desks — CRUD + signal catalog.
Prefix: /api/ai-desks
"""
import logging
from typing import Any, Dict, List, Optional

from fastapi import APIRouter, HTTPException
from pydantic import BaseModel

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/ai-desks", tags=["AI Desks"])


# ── Signal catalog (extensible) ───────────────────────────────────────────────

SIGNAL_CATALOG: List[Dict[str, Any]] = [
    {
        "id":          "ma_cross",
        "label":       "Croisement de moyennes mobiles",
        "description": "Détecte les croisements bullish/bearish entre deux MAs",
        "desk_type":   "technical",
        "params": {
            "pairs": {
                "type": "pairs", "label": "Paires MA",
                "default": [["MA50", "MA200"], ["MA50", "MA100"]],
                "options": ["MA20", "MA50", "MA100", "MA200"],
            },
        },
    },
    {
        "id":          "rsi_extreme",
        "label":       "RSI extrême",
        "description": "Signal quand le RSI dépasse les seuils de surachat/survente",
        "desk_type":   "technical",
        "params": {
            "period":     {"type": "int",   "label": "Période",    "default": 14, "min": 5, "max": 50},
            "oversold":   {"type": "int",   "label": "Survente",   "default": 30, "min": 10, "max": 45},
            "overbought": {"type": "int",   "label": "Surachat",   "default": 70, "min": 55, "max": 90},
        },
    },
    {
        "id":          "bb_squeeze",
        "label":       "Squeeze Bollinger Bands",
        "description": "Détecte quand les bandes BB se resserrent en dessous d'un seuil",
        "desk_type":   "technical",
        "params": {
            "period":          {"type": "int",   "label": "Période",  "default": 20, "min": 10, "max": 50},
            "std":             {"type": "float", "label": "Std dev",   "default": 2.0, "min": 1.0, "max": 3.0},
            "width_threshold": {"type": "float", "label": "Seuil width", "default": 0.05, "min": 0.01, "max": 0.2},
        },
    },
    {
        "id":          "new_52w_extreme",
        "label":       "Nouveau 52 semaines extrême",
        "description": "Nouveau plus haut ou plus bas sur 52 semaines (avec buffer)",
        "desk_type":   "technical",
        "params": {
            "buffer_pct": {"type": "float", "label": "Buffer %", "default": 0.5, "min": 0.0, "max": 5.0},
        },
    },
    {
        "id":          "price_gap",
        "label":       "Gap de prix",
        "description": "Gap d'ouverture significatif par rapport à la clôture précédente",
        "desk_type":   "technical",
        "params": {
            "min_gap_pct": {"type": "float", "label": "Gap min %", "default": 1.5, "min": 0.5, "max": 10.0},
        },
    },
    {
        "id":          "volume_spike",
        "label":       "Spike de volume",
        "description": "Volume anormalement élevé par rapport à la moyenne mobile",
        "desk_type":   "technical",
        "params": {
            "ma_period":     {"type": "int",   "label": "Période MA vol", "default": 20, "min": 5, "max": 50},
            "spike_factor":  {"type": "float", "label": "Facteur spike",  "default": 2.5, "min": 1.5, "max": 10.0},
        },
    },
    {
        "id":          "macd_crossover",
        "label":       "Croisement MACD",
        "description": "Croisement de la ligne MACD avec la ligne signal",
        "desk_type":   "technical",
        "params": {
            "fast":   {"type": "int", "label": "EMA rapide", "default": 12, "min": 5,  "max": 30},
            "slow":   {"type": "int", "label": "EMA lente",  "default": 26, "min": 15, "max": 60},
            "signal": {"type": "int", "label": "Signal",     "default": 9,  "min": 3,  "max": 20},
        },
    },
    # ── Wavelets — décomposition en bandes de fréquence sur la watchlist ────
    {
        "id":          "wavelet_engine",
        "label":       "Ondelettes — moteur",
        "description": "Paramètres partagés du calcul (désactive tous les signaux ondelettes si décoché)",
        "desk_type":   "technical",
        "params": {
            "num_levels":    {"type": "int",    "label": "Nb bandes",    "default": 4,    "min": 2,   "max": 6},
            "wavelet":       {"type": "select",  "label": "Famille",     "default": "gmw", "options": ["gmw", "morlet", "bump"]},
            "method":        {"type": "select",  "label": "Méthode",     "default": "cwt", "options": ["cwt", "ssq"]},
            "lookback_days": {"type": "int",    "label": "Lookback (j)", "default": 120,  "min": 60,  "max": 250},
        },
    },
    {
        "id":          "wavelet_extremum",
        "label":       "Ondelettes — extremum",
        "description": "Pic ou creux confirmé sur une bande",
        "desk_type":   "technical",
        "params": {},
    },
    {
        "id":          "wavelet_level_threshold",
        "label":       "Ondelettes — seuil de niveau",
        "description": "La bande dépasse un seuil de z-score causal (sur/sous-achetée)",
        "desk_type":   "technical",
        "params": {
            "threshold_k": {"type": "float", "label": "Seuil (écarts-type)", "default": 2.0, "min": 1.0, "max": 4.0},
        },
    },
    {
        "id":          "wavelet_trend_flatten",
        "label":       "Ondelettes — tendance puis tassement",
        "description": "Forte pente suivie d'un aplatissement — signal de fin de mouvement",
        "desk_type":   "technical",
        "params": {
            "trend_days":          {"type": "int",   "label": "Jours tendance",   "default": 10,  "min": 3,  "max": 30},
            "flatten_days":        {"type": "int",   "label": "Jours tassement",  "default": 5,   "min": 2,  "max": 15},
            "trend_threshold_k":   {"type": "float", "label": "Seuil tendance",   "default": 1.0, "min": 0.3, "max": 3.0},
            "flatten_threshold_k": {"type": "float", "label": "Seuil tassement",  "default": 0.3, "min": 0.1, "max": 1.5},
        },
    },
    {
        "id":          "wavelet_acceleration",
        "label":       "Ondelettes — déceleration/accélération",
        "description": "Accélération soutenue en sens inverse de la pente — signal de retournement",
        "desk_type":   "technical",
        "params": {
            "accel_days":       {"type": "int",   "label": "Jours consécutifs", "default": 3,   "min": 1,  "max": 10},
            "accel_threshold_k":{"type": "float", "label": "Seuil (écarts-type)", "default": 1.5, "min": 0.5, "max": 4.0},
        },
    },
    {
        "id":          "wavelet_band_cross",
        "label":       "Ondelettes — croisement de bandes",
        "description": "Une bande croise une bande secondaire",
        "desk_type":   "technical",
        "params": {
            "secondary_band": {"type": "int", "label": "Index bande secondaire", "default": 1, "min": 0, "max": 5},
        },
    },
    {
        "id":          "wavelet_ridge_shift",
        "label":       "Ondelettes — bascule de ridge",
        "description": "Le cycle dominant (ridge SSQ) dévie de sa moyenne — nécessite méthode = ssq",
        "desk_type":   "technical",
        "params": {
            "threshold_k": {"type": "float", "label": "Seuil (écarts-type)", "default": 2.0, "min": 1.0, "max": 4.0},
        },
    },
    {
        "id":          "wavelet_energy_threshold",
        "label":       "Ondelettes — seuil d'énergie",
        "description": "L'énergie d'une bande dépasse un seuil — nécessite méthode = ssq",
        "desk_type":   "technical",
        "params": {
            "threshold_k": {"type": "float", "label": "Seuil (écarts-type)", "default": 2.0, "min": 1.0, "max": 4.0},
        },
    },
    # ── Sentiment signals ───────────────────────────────────────────────────
    {
        "id":          "vix_level",
        "label":       "VIX — franchissement de seuil",
        "description": "Signal quand le VIX franchit un niveau clé (20, 25, 30, 35, 45)",
        "desk_type":   "sentiment",
        "params": {
            "thresholds": {"type": "multi_int", "label": "Niveaux VIX", "default": [20, 25, 30, 35, 45]},
        },
    },
    {
        "id":          "vix_spike",
        "label":       "VIX — spike journalier",
        "description": "VIX progresse de X% ou plus en une séance",
        "desk_type":   "sentiment",
        "params": {
            "min_pct_change": {"type": "float", "label": "Variation min %", "default": 15.0, "min": 5.0, "max": 50.0},
        },
    },
    {
        "id":          "vix_term_structure",
        "label":       "VIX — inversion structure de terme",
        "description": "VIX9D > VIX (peur concentrée court terme)",
        "desk_type":   "sentiment",
        "params": {
            "inversion_threshold": {"type": "float", "label": "Ratio VIX9D/VIX min", "default": 1.05, "min": 1.0, "max": 1.5},
        },
    },
    {
        "id":          "vvix_extreme",
        "label":       "VVIX extrême",
        "description": "VVIX (volatilité du VIX) dépasse un seuil",
        "desk_type":   "sentiment",
        "params": {
            "threshold": {"type": "float", "label": "Seuil VVIX", "default": 100.0, "min": 80.0, "max": 150.0},
        },
    },
    {
        "id":          "skew_extreme",
        "label":       "CBOE SKEW extrême",
        "description": "SKEW index très haut (protection tail coûteuse) ou très bas (complaisance)",
        "desk_type":   "sentiment",
        "params": {
            "low_threshold":  {"type": "float", "label": "Seuil bas",  "default": 120.0, "min": 100.0, "max": 130.0},
            "high_threshold": {"type": "float", "label": "Seuil haut", "default": 145.0, "min": 135.0, "max": 170.0},
        },
    },
]


# ── Schemas ───────────────────────────────────────────────────────────────────

DESK_TYPES = {"news", "fundamental", "technical", "eco", "report", "sentiment"}


class AIDeskUpsert(BaseModel):
    name:          str
    type:          str   # one of DESK_TYPES
    active:        bool = True
    system_prompt: str = ""
    instruments:   List[str] = []
    config:        Dict[str, Any] = {}


# ── Endpoints ─────────────────────────────────────────────────────────────────

@router.get("")
def list_desks() -> List[Dict[str, Any]]:
    from services.database import get_all_ai_desks
    return get_all_ai_desks()


@router.get("/signal-catalog")
def get_signal_catalog() -> List[Dict[str, Any]]:
    return SIGNAL_CATALOG


@router.get("/{desk_id}")
def get_desk(desk_id: int) -> Dict[str, Any]:
    from services.database import get_all_ai_desks
    desks = get_all_ai_desks()
    desk = next((d for d in desks if d["id"] == desk_id), None)
    if not desk:
        raise HTTPException(404, f"Desk {desk_id} not found")
    return desk


@router.post("", status_code=201)
def create_desk(body: AIDeskUpsert) -> Dict[str, Any]:
    from services.database import upsert_ai_desk
    new_id = upsert_ai_desk(body.dict())
    return {"id": new_id, "status": "created"}


@router.put("/{desk_id}")
def update_desk(desk_id: int, body: AIDeskUpsert) -> Dict[str, Any]:
    from services.database import get_all_ai_desks, update_ai_desk_by_id
    desks = get_all_ai_desks()
    desk = next((d for d in desks if d["id"] == desk_id), None)
    if not desk:
        raise HTTPException(404, f"Desk {desk_id} not found")
    update_ai_desk_by_id(desk_id, body.dict())
    return {"status": "updated"}


@router.patch("/{desk_id}/toggle")
def toggle_desk_active(desk_id: int) -> Dict[str, Any]:
    from services.database import get_all_ai_desks, update_ai_desk_by_id
    desks = get_all_ai_desks()
    desk = next((d for d in desks if d["id"] == desk_id), None)
    if not desk:
        raise HTTPException(404, f"Desk {desk_id} not found")
    desk["active"] = not desk["active"]
    update_ai_desk_by_id(desk_id, desk)
    return {"status": "toggled", "active": desk["active"]}


@router.delete("/{desk_id}")
def delete_desk(desk_id: int) -> Dict[str, Any]:
    from services.database import get_all_ai_desks, delete_ai_desk
    desks = get_all_ai_desks()
    desk = next((d for d in desks if d["id"] == desk_id), None)
    if not desk:
        raise HTTPException(404, f"Desk {desk_id} not found")
    delete_ai_desk(desk["name"])
    return {"status": "deleted"}
