from typing import Any, Dict, List, Optional

from fastapi import APIRouter, HTTPException, Query
from pydantic import BaseModel

from services.option_chain import get_chain_slice
from services.vol_surface import build_surface, apply_scenario
from services.strategy_engine import payoff_curves, DEFAULT_CONTRACT_SIZE
from services.strategy_optimizer import optimize as run_optimizer
from services.database import (
    save_scenario, get_scenarios, delete_scenario,
    save_strategy, get_saved_strategies, delete_saved_strategy,
)

router = APIRouter(prefix="/api/strategy-builder", tags=["strategy-builder"])


class LegIn(BaseModel):
    expiry_date: str
    days_to_expiry: int
    strike: float
    option_type: str  # "call" | "put"
    position: str  # "long" | "short"
    quantity: int = 1


class ScenarioIn(BaseModel):
    symbol: str
    horizon_days: int = 8
    spot_shock_pct: float = 0.0
    iv_level_shift: float = 0.0
    skew_tilt: float = 0.0
    term_shift: float = 0.0
    manual_grid: Optional[List[Dict[str, Any]]] = None
    rate: float = 0.05
    n_expiries: int = 3
    contract_size: float = DEFAULT_CONTRACT_SIZE


class PriceRequest(BaseModel):
    scenario: ScenarioIn
    legs: List[LegIn]


class ConstraintsIn(BaseModel):
    max_legs: int = 4
    delta_threshold: Optional[float] = 0.15
    max_loss_cap: Optional[float] = None
    objective: str = "net_pnl"  # "net_pnl" | "return_on_risk" | "prob_weighted"
    top_n: int = 20


class OptimizeRequest(BaseModel):
    scenario: ScenarioIn
    constraints: ConstraintsIn


class ScenarioSaveRequest(BaseModel):
    symbol: str
    label: Optional[str] = ""
    horizon_days: int
    spot_shock_pct: float
    iv_level_shift: float
    skew_tilt: float
    term_shift: float
    manual_grid: Optional[List[Dict[str, Any]]] = None


class StrategySaveRequest(BaseModel):
    scenario_id: Optional[str] = None
    symbol: str
    template_name: Optional[str] = ""
    objective: Optional[str] = ""
    legs: List[LegIn]
    entry_cost: Optional[float] = None
    max_gain: Optional[float] = None
    max_loss: Optional[float] = None
    net_pnl_scenario: Optional[float] = None
    net_delta: Optional[float] = None
    notes: Optional[str] = ""


def _build_surfaces(scenario: ScenarioIn):
    chain_slice = get_chain_slice(scenario.symbol, scenario.horizon_days, scenario.n_expiries)
    surface_now = build_surface(chain_slice)
    surface_scenario = apply_scenario(
        surface_now,
        spot_shock_pct=scenario.spot_shock_pct,
        iv_level_shift=scenario.iv_level_shift,
        skew_tilt=scenario.skew_tilt,
        term_shift=scenario.term_shift,
        manual_grid=scenario.manual_grid,
    )
    return chain_slice, surface_now, surface_scenario


@router.get("/chain")
def chain(
    symbol: str = Query(...),
    horizon_days: int = Query(8),
    n_expiries: int = Query(3),
):
    try:
        return get_chain_slice(symbol, horizon_days, n_expiries)
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))


@router.post("/price")
def price(req: PriceRequest):
    if not req.legs:
        raise HTTPException(status_code=400, detail="Au moins une jambe est requise")
    if len(req.legs) > 4:
        raise HTTPException(status_code=400, detail="4 jambes maximum")

    try:
        chain_slice, surface_now, surface_scenario = _build_surfaces(req.scenario)
    except ValueError as e:
        raise HTTPException(status_code=404, detail=str(e))

    legs = [leg.model_dump() for leg in req.legs]
    result = payoff_curves(
        legs, chain_slice, surface_now, surface_scenario,
        req.scenario.horizon_days, req.scenario.rate,
        contract_size=req.scenario.contract_size,
    )
    result["spot"] = chain_slice["spot"]
    result["scenario_spot"] = surface_scenario.spot
    result["proxy"] = chain_slice["proxy"]
    return result


@router.post("/optimize")
def optimize(req: OptimizeRequest):
    if req.constraints.max_legs > 4:
        raise HTTPException(status_code=400, detail="4 jambes maximum")
    try:
        results = run_optimizer(
            symbol=req.scenario.symbol,
            horizon_days=req.scenario.horizon_days,
            spot_shock_pct=req.scenario.spot_shock_pct,
            iv_level_shift=req.scenario.iv_level_shift,
            skew_tilt=req.scenario.skew_tilt,
            term_shift=req.scenario.term_shift,
            manual_grid=req.scenario.manual_grid,
            n_expiries=req.scenario.n_expiries,
            rate=req.scenario.rate,
            constraints=req.constraints.model_dump(),
            objective=req.constraints.objective,
            top_n=req.constraints.top_n,
            contract_size=req.scenario.contract_size,
        )
    except Exception as e:
        import traceback
        from services.database import log_system_event
        tb = traceback.format_exc()
        log_system_event(
            level="ERROR", source="strategy_optimizer",
            message=f"Optimize failed for {req.scenario.symbol}: {e}",
            ticker=req.scenario.symbol, details={"error": str(e), "traceback": tb},
        )
        status = 404 if isinstance(e, ValueError) else 500
        raise HTTPException(status_code=status, detail=f"{e}")
    return results


@router.post("/scenarios")
def create_scenario(req: ScenarioSaveRequest):
    scenario_id = save_scenario(req.model_dump())
    return {"id": scenario_id}


@router.get("/scenarios")
def list_scenarios(symbol: Optional[str] = Query(None)):
    return get_scenarios(symbol)


@router.delete("/scenarios/{scenario_id}")
def remove_scenario(scenario_id: str):
    if not delete_scenario(scenario_id):
        raise HTTPException(status_code=404, detail="Scénario non trouvé")
    return {"deleted": True}


@router.post("/saved")
def create_saved_strategy(req: StrategySaveRequest):
    payload = req.model_dump()
    payload["legs"] = [leg for leg in payload["legs"]]
    strategy_id = save_strategy(payload)
    return {"id": strategy_id}


@router.get("/saved")
def list_saved_strategies(symbol: Optional[str] = Query(None)):
    return get_saved_strategies(symbol)


@router.delete("/saved/{strategy_id}")
def remove_saved_strategy(strategy_id: str):
    if not delete_saved_strategy(strategy_id):
        raise HTTPException(status_code=404, detail="Stratégie non trouvée")
    return {"deleted": True}
