"""CV6 F&O Router"""
from __future__ import annotations
from datetime import date, datetime
from typing import Any, Dict, List, Optional
from fastapi import APIRouter, HTTPException, Depends
from pydantic import BaseModel
from loguru import logger
from app.fno.fno_engine import fno_engine, calc_fno_charges
from app.fno.fno_symbols import fno_symbols, LOT_SIZES
from app.fno.options_pricing import price_option, calc_max_pain
from app.core.dependencies import get_current_user

router = APIRouter(prefix="/fno", tags=["F&O"])

class TradeRequest(BaseModel):
    underlying:   str   = "NIFTY"
    spot_price:   float
    option_type:  str   = "CE"
    lots:         int   = 1
    strike:       Optional[float] = None
    strike_style: str   = "OTM1"
    sl_pct:       float = 50.0
    target_pct:   float = 100.0
    broker:       str   = "angelone"
    paper:        bool  = True
    iv_pct:       float = 15.0

class CloseRequest(BaseModel):
    trade_id:     str
    exit_premium: float
    broker:       str  = "angelone"
    paper:        bool = True

class MonitorRequest(BaseModel):
    premiums: dict

class PriceRequest(BaseModel):
    underlying:  str   = "NIFTY"
    spot:        float
    strike:      float
    option_type: str   = "CE"
    days_to_exp: float = 7.0
    iv_pct:      float = 15.0

@router.get("/analyse")
def analyse_direction(underlying: str = "NIFTY", use_ai: bool = True):
    return fno_engine.analyse_direction(underlying=underlying, use_ai=use_ai)

@router.post("/trade")
def place_trade(req: TradeRequest, _user=Depends(get_current_user)):
    result = fno_engine.place_trade(
        underlying=req.underlying, spot_price=req.spot_price,
        option_type=req.option_type, lots=req.lots,
        strike=req.strike, strike_style=req.strike_style,
        sl_pct=req.sl_pct, target_pct=req.target_pct,
        broker_name=req.broker, paper=req.paper, iv_pct=req.iv_pct,
    )
    if not result.get("success"):
        raise HTTPException(400, result.get("message", "Trade failed"))
    return result

@router.post("/close")
def close_trade(req: CloseRequest, _user=Depends(get_current_user)):
    result = fno_engine.close_trade(req.trade_id, req.exit_premium, req.broker, req.paper)
    if not result.get("success"):
        raise HTTPException(404, result.get("message"))
    return result

@router.post("/monitor")
def monitor_positions(req: MonitorRequest, _user=Depends(get_current_user)):
    return {"actions": fno_engine.monitor_positions(req.premiums)}

@router.get("/positions")
def open_positions(_user=Depends(get_current_user)):
    return {"positions": fno_engine.open_positions()}

@router.get("/trades")
def all_trades(_user=Depends(get_current_user)):
    return {"trades": fno_engine.all_trades()}

@router.get("/pnl")
def portfolio_pnl(_user=Depends(get_current_user)):
    return fno_engine.portfolio_pnl()

@router.get("/expiries")
def upcoming_expiries(underlying: str = "NIFTY", count: int = 4):
    expiries = fno_symbols.upcoming_expiries(underlying, count)
    return {"underlying": underlying, "expiries": [str(e) for e in expiries]}

@router.get("/strikes")
def available_strikes(underlying: str = "NIFTY"):
    expiry  = fno_symbols.nearest_expiry(underlying)
    strikes = fno_symbols.strikes(underlying, expiry)
    return {"underlying": underlying, "expiry": str(expiry),
            "lot_size": LOT_SIZES.get(underlying.upper(), 75), "strikes": strikes}

@router.post("/price")
def price_contract(req: PriceRequest):
    g   = price_option(req.spot, req.strike, req.days_to_exp, req.iv_pct, req.option_type)
    lot = LOT_SIZES.get(req.underlying.upper(), 75)
    return {
        "underlying": req.underlying, "strike": req.strike,
        "option_type": req.option_type, "lot_size": lot,
        "fair_value": g.fair_value, "lot_cost": round(g.fair_value * lot, 2),
        "greeks": {"delta": g.delta, "gamma": g.gamma, "theta": g.theta,
                   "theta_per_lot": round(g.theta * lot, 2), "vega": g.vega,
                   "iv": g.iv, "moneyness": g.moneyness,
                   "intrinsic": g.intrinsic, "time_value": g.time_value},
    }

@router.get("/lot-sizes")
def lot_sizes():
    return LOT_SIZES


# ── Option Chain (NSE real data) ───────────────────────────────────────────────

class ChainRequest(BaseModel):
    symbol:  str   = "NIFTY"
    expiry:  str   = ""
    spot:    float = 0.0

class OIRequest(BaseModel):
    symbol:  str   = "NIFTY"
    expiry:  str   = ""
    spot:    float = 0.0

@router.post("/chain")
def option_chain(req: ChainRequest):
    """Fetch full option chain from NSE. Falls back to Black-Scholes if NSE unavailable."""
    try:
        from app.market.nse_data import _get, _BASE
        data = _get(f"{_BASE}/api/option-chain-indices",
                    params={"symbol": req.symbol.upper()})
        if not data:
            raise ValueError("NSE returned no data")

        records     = data.get("records", {}).get("data", [])
        spot_price  = data.get("records", {}).get("underlyingValue", req.spot) or req.spot
        expiry_list = data.get("records", {}).get("expiryDates", [])

        # pick expiry: requested or nearest
        target_expiry = req.expiry if req.expiry else (expiry_list[0] if expiry_list else "")

        calls, puts = [], []
        oi_data_for_pain: Dict[float, Dict] = {}

        for rec in records:
            exp = rec.get("expiryDate", "")
            if target_expiry and exp != target_expiry:
                continue
            strike = float(rec.get("strikePrice", 0))
            ce = rec.get("CE") or {}
            pe = rec.get("PE") or {}

            def _row(d: dict, ot: str) -> dict:
                return {
                    "strike":    strike,
                    "oi":        int(d.get("openInterest", 0)),
                    "oi_change": int(d.get("changeinOpenInterest", 0)),
                    "iv":        round(float(d.get("impliedVolatility", 0) or 0), 2),
                    "ltp":       round(float(d.get("lastPrice", 0) or 0), 2),
                    "change":    round(float(d.get("change", 0) or 0), 2),
                    "pchange":   round(float(d.get("pChange", 0) or 0), 2),
                    "greeks":    {},
                }

            if ce:
                calls.append(_row(ce, "CE"))
            if pe:
                puts.append(_row(pe, "PE"))

            oi_data_for_pain[strike] = {
                "call_oi": int(ce.get("openInterest", 0)) if ce else 0,
                "put_oi":  int(pe.get("openInterest", 0)) if pe else 0,
            }

        # ATM
        step = 100 if "BANK" in req.symbol.upper() else 50
        atm  = round(round(spot_price / step) * step, 0)

        return {
            "symbol":  req.symbol.upper(),
            "expiry":  target_expiry,
            "expiries": expiry_list[:6],
            "spot":    spot_price,
            "atm":     atm,
            "calls":   sorted(calls, key=lambda x: x["strike"]),
            "puts":    sorted(puts,  key=lambda x: x["strike"]),
            "max_pain": calc_max_pain(oi_data_for_pain),
        }
    except Exception as e:
        logger.warning(f"[FnO chain] NSE fetch failed: {e}. Returning empty chain.")
        return {"symbol": req.symbol, "expiry": req.expiry, "spot": req.spot,
                "atm": req.spot, "calls": [], "puts": [], "expiries": [], "max_pain": None}


@router.post("/oi-analysis")
def oi_analysis(req: OIRequest):
    """OI analysis — PCR, max pain, top-5 buildup, sentiment."""
    try:
        from app.market.nse_data import _get, _BASE
        data = _get(f"{_BASE}/api/option-chain-indices",
                    params={"symbol": req.symbol.upper()})
        if not data:
            raise ValueError("NSE returned no data")

        records    = data.get("records", {}).get("data", [])
        expiry_list = data.get("records", {}).get("expiryDates", [])
        target_exp  = req.expiry or (expiry_list[0] if expiry_list else "")

        call_oi: Dict[float, int] = {}
        put_oi:  Dict[float, int] = {}
        call_oi_chg: Dict[float, int] = {}
        put_oi_chg:  Dict[float, int] = {}

        for rec in records:
            if target_exp and rec.get("expiryDate", "") != target_exp:
                continue
            strike = float(rec.get("strikePrice", 0))
            ce = rec.get("CE") or {}
            pe = rec.get("PE") or {}
            call_oi[strike]     = int(ce.get("openInterest", 0))
            put_oi[strike]      = int(pe.get("openInterest", 0))
            call_oi_chg[strike] = int(ce.get("changeinOpenInterest", 0))
            put_oi_chg[strike]  = int(pe.get("changeinOpenInterest", 0))

        total_call = sum(call_oi.values())
        total_put  = sum(put_oi.values())
        pcr        = round(total_put / total_call, 3) if total_call else 0.0

        call_resistance = max(call_oi, key=call_oi.get) if call_oi else None
        put_support     = max(put_oi,  key=put_oi.get)  if put_oi  else None

        oi_dict = {s: {"call_oi": call_oi.get(s, 0), "put_oi": put_oi.get(s, 0)}
                   for s in call_oi}
        max_pain = calc_max_pain(oi_dict)

        # Top 5 OI
        top5_calls = sorted(call_oi.items(), key=lambda x: x[1], reverse=True)[:5]
        top5_puts  = sorted(put_oi.items(),  key=lambda x: x[1], reverse=True)[:5]
        call_oi_top5 = [[s, o, call_oi_chg.get(s, 0)] for s, o in top5_calls]
        put_oi_top5  = [[s, o, put_oi_chg.get(s, 0)]  for s, o in top5_puts]

        if pcr > 1.3:
            sentiment = "BULLISH"
        elif pcr < 0.7:
            sentiment = "BEARISH"
        else:
            sentiment = "NEUTRAL"

        return {
            "symbol":           req.symbol.upper(),
            "expiry":           target_exp,
            "pcr":              pcr,
            "total_call_oi":    total_call,
            "total_put_oi":     total_put,
            "call_resistance":  call_resistance,
            "put_support":      put_support,
            "max_pain":         max_pain,
            "sentiment":        sentiment,
            "call_oi_top5":     call_oi_top5,
            "put_oi_top5":      put_oi_top5,
            "atm":              req.spot,
        }
    except Exception as e:
        logger.warning(f"[FnO OI] {e}")
        return {"symbol": req.symbol, "pcr": 0.0, "sentiment": "No Data",
                "call_resistance": None, "put_support": None, "max_pain": None,
                "call_oi_top5": [], "put_oi_top5": []}


# ── Margin Calculator ──────────────────────────────────────────────────────────

class MarginRequest(BaseModel):
    symbol:      str   = "NIFTY"
    strike:      float = 0.0
    lots:        int   = 1
    option_type: str   = "CE"
    ltp:         float = 0.0

@router.post("/margin")
def margin_calc(req: MarginRequest):
    lot_size  = LOT_SIZES.get(req.symbol.upper(), 75)
    total_qty = req.lots * lot_size
    turnover  = req.ltp * total_qty

    # Buyer margin = premium outflow + charges
    buy_charges    = calc_fno_charges(req.ltp, total_qty, "BUY")
    buyer_margin   = round(turnover + buy_charges, 2)

    # Writer margin (approximate SPAN + Exposure)
    # SPAN ≈ 5% of contract value; Exposure ≈ 1.5% of notional
    # This is approximate — real SPAN requires exchange RMS calculation
    notional       = req.strike * total_qty
    writer_span     = round(notional * 0.05, 2)
    writer_exposure = round(notional * 0.015, 2)
    writer_total    = round(writer_span + writer_exposure, 2)

    sell_charges   = calc_fno_charges(req.ltp, total_qty, "SELL")

    return {
        "symbol":          req.symbol.upper(),
        "strike":          req.strike,
        "option_type":     req.option_type.upper(),
        "lots":            req.lots,
        "lot_size":        lot_size,
        "total_qty":       total_qty,
        "ltp":             req.ltp,
        "buyer_margin":    buyer_margin,
        "buy_charges":     buy_charges,
        "sell_charges":    sell_charges,
        "writer_span":     writer_span,
        "writer_exposure": writer_exposure,
        "writer_total":    writer_total,
        "note":            "Writer margin is approximate SPAN estimate. Real margin may vary.",
    }
