from fastapi import APIRouter, HTTPException
from typing import Any, Dict, List, Optional
import math
from pydantic import BaseModel
from services.database import (
    get_macro_regime_history, get_geo_alert_history, get_trade_entry_prices,
    get_closed_trades, close_trade, update_trade_exit_params,
    get_trade_entry_by_id, get_config, set_config, reset_journal_history,
    _fetch_live_prices, _trade_maturity, get_skipped_trades, delete_trade,
)
import json


def _sanitize(obj: Any) -> Any:
    """Replace NaN/Inf with None recursively for JSON compliance."""
    if isinstance(obj, dict):
        return {k: _sanitize(v) for k, v in obj.items()}
    if isinstance(obj, list):
        return [_sanitize(v) for v in obj]
    if isinstance(obj, float) and (math.isnan(obj) or math.isinf(obj)):
        return None
    return obj

router = APIRouter(prefix="/api/journal", tags=["journal"])

# Bearish strategies — P&L is inverted (profit when price falls)
_BEARISH_KEYWORDS = {"bear", "put", "short", "sell", "vente", "baissier"}


def _is_bearish(strategy: str) -> bool:
    s = (strategy or "").lower()
    return any(kw in s for kw in _BEARISH_KEYWORDS)


@router.get("/macro-history")
def macro_history(days: int = 15):
    """Macro regime snapshots for the last N days."""
    return _sanitize({"history": get_macro_regime_history(days), "days": days})


@router.get("/geo-history")
def geo_history(days: int = 30):
    """Geo alert score history for the last N days."""
    return {"history": get_geo_alert_history(days), "days": days}




@router.get("/trade-mtm")
def trade_mtm(days: int = 30):
    """
    Mark-to-market for all logged trade suggestions.
    Enriches with live prices via shared _fetch_live_prices utility.
    """
    entries = get_trade_entry_prices(days)
    tickers_needed = list({(e.get("underlying") or "").upper() for e in entries if e.get("underlying")})
    current_prices = _fetch_live_prices(tickers_needed, timeout=20)

    from datetime import date as _date
    result: List[Dict[str, Any]] = []
    for e in entries:
        ticker = (e.get("underlying") or "").upper()
        entry_price = e.get("entry_price")
        is_closed = e.get("status") == "closed"

        # For closed trades: pnl_pct is fixed from pnl_realized (locked in).
        # For open trades: compute live from current market price.
        if is_closed:
            pnl_realized = e.get("pnl_realized")
            capital = e.get("capital_invested") or entry_price or 0
            if pnl_realized is not None and capital and capital > 0:
                pnl_pct = round(pnl_realized / capital * 100, 2)
            elif e.get("close_price") and entry_price and entry_price > 0:
                raw = (e["close_price"] - entry_price) / entry_price * 100
                pnl_pct = round(-raw if _is_bearish(e.get("strategy", "")) else raw, 2)
            else:
                pnl_pct = None
            current_price = e.get("close_price")
            price_warning = None if pnl_pct is not None else "no_close_price"
        else:
            current_price = current_prices.get(ticker)
            pnl_pct = None
            if entry_price and current_price and entry_price > 0:
                raw_pnl = (current_price - entry_price) / entry_price * 100
                pnl_pct = round(-raw_pnl if _is_bearish(e.get("strategy", "")) else raw_pnl, 2)
            price_warning = None
            if entry_price is None and current_price is None:
                price_warning = "no_price_data"
            elif entry_price is None:
                price_warning = "no_entry_price"
            elif current_price is None:
                price_warning = "no_live_price"

        days_held = None
        try:
            days_held = (_date.today() - _date.fromisoformat(e["entry_date"])).days
        except Exception:
            pass

        horizon = e.get("horizon_days") or 90
        maturity = "closed" if is_closed else _trade_maturity(days_held or 0, horizon)

        defaults = _get_exit_defaults()
        target = e.get("target_pct") if e.get("target_pct") is not None else defaults.get("target_pct", 30.0)
        stop = e.get("stop_loss_pct") if e.get("stop_loss_pct") is not None else defaults.get("stop_loss_pct", -50.0)

        alert_type = None
        if not is_closed and pnl_pct is not None:
            if pnl_pct >= target:
                alert_type = "target_reached"
            elif pnl_pct <= stop:
                alert_type = "stop_loss"

        result.append({
            **e,
            "current_price": current_price,
            "pnl_pct": pnl_pct,
            "price_warning": price_warning,
            "days_held": days_held,
            "direction": "bearish" if _is_bearish(e.get("strategy", "")) else "bullish",
            "maturity": maturity,
            "alert_type": alert_type,
            "target_pct": target,
            "stop_loss_pct": stop,
        })

    return _sanitize({"trades": result, "days": days, "tickers_fetched": len(current_prices)})


def _get_exit_defaults() -> Dict[str, Any]:
    raw = get_config("exit_defaults")
    if raw:
        try:
            return json.loads(raw)
        except Exception:
            pass
    return {"target_pct": 30.0, "stop_loss_pct": -50.0,
            "signal_reversal_mode": "badge_only", "signal_reversal_threshold": 25}


@router.get("/exit-defaults")
def exit_defaults():
    return _get_exit_defaults()


class ExitDefaultsRequest(BaseModel):
    target_pct: Optional[float] = None
    stop_loss_pct: Optional[float] = None
    signal_reversal_mode: Optional[str] = None
    signal_reversal_threshold: Optional[float] = None


@router.put("/exit-defaults")
def save_exit_defaults(body: ExitDefaultsRequest):
    current = _get_exit_defaults()
    if body.target_pct is not None:
        current["target_pct"] = body.target_pct
    if body.stop_loss_pct is not None:
        current["stop_loss_pct"] = body.stop_loss_pct
    if body.signal_reversal_mode is not None:
        current["signal_reversal_mode"] = body.signal_reversal_mode
    if body.signal_reversal_threshold is not None:
        current["signal_reversal_threshold"] = body.signal_reversal_threshold
    set_config("exit_defaults", json.dumps(current))
    return current


@router.get("/closed-trades")
def closed_trades(days: int = 180):
    trades = get_closed_trades(days)
    if not trades:
        return _sanitize({"trades": [], "days": days, "stats": {}})
    pnls = [t["pnl_realized"] for t in trades if t.get("pnl_realized") is not None]
    wins = [p for p in pnls if p >= 0]
    losses = [p for p in pnls if p < 0]
    stats = {
        "total": len(trades),
        "with_pnl": len(pnls),
        "win_rate": round(len(wins) / len(pnls) * 100, 1) if pnls else None,
        "avg_pnl": round(sum(pnls) / len(pnls), 2) if pnls else None,
        "total_pnl": round(sum(pnls), 2) if pnls else None,
        "avg_win": round(sum(wins) / len(wins), 2) if wins else None,
        "avg_loss": round(sum(losses) / len(losses), 2) if losses else None,
        "best": max(pnls, default=None),
        "worst": min(pnls, default=None),
    }
    return _sanitize({"trades": trades, "days": days, "stats": stats})


class CloseTradeRequest(BaseModel):
    close_price: float
    pnl_realized: Optional[float] = None
    close_reason: str = "manual"
    close_note: str = ""


class ExitParamsRequest(BaseModel):
    target_pct: Optional[float] = None
    stop_loss_pct: Optional[float] = None
    signal_threshold: Optional[float] = None


@router.patch("/trades/{trade_id}/exit-params")
def set_exit_params(trade_id: int, body: ExitParamsRequest):
    ok = update_trade_exit_params(
        trade_id,
        target_pct=body.target_pct,
        stop_loss_pct=body.stop_loss_pct,
        signal_threshold=body.signal_threshold,
    )
    if not ok:
        raise HTTPException(404, "Trade non trouvé")
    return {"updated": True}


@router.patch("/trades/{trade_id}/close")
def close_trade_endpoint(trade_id: int, body: CloseTradeRequest):
    trade = get_trade_entry_by_id(trade_id)
    if not trade:
        raise HTTPException(404, "Trade non trouvé")
    if trade.get("status") == "closed":
        raise HTTPException(409, "Trade déjà clôturé")

    pnl = body.pnl_realized
    if pnl is None and trade.get("entry_price") and body.close_price > 0:
        raw = (body.close_price - trade["entry_price"]) / trade["entry_price"] * 100
        is_bearish = any(kw in (trade.get("strategy") or "").lower()
                         for kw in _BEARISH_KEYWORDS)
        pnl = round(-raw if is_bearish else raw, 2)

    ok = close_trade(trade_id, body.close_price, pnl, body.close_reason, body.close_note)
    if not ok:
        raise HTTPException(409, "Impossible de clôturer ce trade")
    return {"closed": True, "trade_id": trade_id, "pnl_realized": pnl}


@router.get("/portfolio-risk")
def portfolio_risk():
    """Risk analysis of the open simulation portfolio (asset class concentration, conflicts)."""
    import json as _json
    from services.portfolio_risk import analyze_simulation_portfolio
    from services.database import get_system_logs
    result = analyze_simulation_portfolio()

    # Attach latest AI monitor recommendation if available
    logs = get_system_logs(source="portfolio_monitor", limit=1)
    if logs:
        raw = logs[0].get("details")
        if raw:
            try:
                result["ai_monitor"] = _json.loads(raw)
                result["ai_monitor_ts"] = logs[0].get("ts")
            except Exception:
                pass

    return _sanitize(result)


@router.get("/portfolio-risk-radar")
def portfolio_risk_radar():
    """5-axis risk radar (Concentration/Volatility/Correlation/Exposure/Drawdown) for the
    Cockpit's Risk card. Separate from /portfolio-risk above — this one makes live
    yfinance calls (per-position volatility + a correlation matrix), heavier and slower,
    so it's not bundled into the lighter endpoint other pages may poll more often."""
    from services.portfolio_risk import compute_portfolio_risk_radar
    return _sanitize(compute_portfolio_risk_radar())


class TradeCheckRequest(BaseModel):
    underlying: str
    strategy: str
    asset_class: str = ""


@router.post("/trade-check")
def trade_check(body: TradeCheckRequest):
    """Pre-entry check: would adding this trade create conflicts or concentration issues?"""
    from services.portfolio_risk import check_new_trade
    return check_new_trade(body.underlying, body.strategy, body.asset_class)


@router.get("/skipped-trades")
def skipped_trades_endpoint(days: int = 30):
    """Trades suggested by cycle that didn't pass any risk profile threshold."""
    trades = get_skipped_trades(days)
    return _sanitize({"trades": trades, "days": days, "count": len(trades)})


@router.delete("/trades/{trade_id}")
def delete_trade_endpoint(trade_id: int):
    ok = delete_trade(trade_id)
    if not ok:
        raise HTTPException(404, "Trade non trouvé")
    return {"deleted": True, "trade_id": trade_id}


@router.delete("/reset")
def reset_journal():
    """Truncate all journal history (trades, macro, geo, cycles). Irreversible."""
    reset_journal_history()
    return {"reset": True, "message": "Journal de bord réinitialisé"}


@router.get("/summary")
def journal_summary():
    """Quick stats for the Journal de Bord header."""
    macro = get_macro_regime_history(15)
    geo = get_geo_alert_history(30)
    trades = get_trade_entry_prices(30)

    # Detect regime transitions (consecutive different dominants)
    transitions = []
    for i in range(1, len(macro)):
        if macro[i - 1]["dominant"] != macro[i]["dominant"]:
            transitions.append({
                "from": macro[i]["dominant"],
                "to": macro[i - 1]["dominant"],
                "at": macro[i - 1]["timestamp"],
            })

    return {
        "macro_snapshots": len(macro),
        "regime_transitions": transitions[:5],
        "current_dominant": macro[0]["dominant"] if macro else None,
        "geo_alerts": len(geo),
        "avg_geo_score": round(sum(g["geo_score"] for g in geo) / len(geo), 1) if geo else None,
        "max_geo_score": max((g["geo_score"] for g in geo), default=None),
        "trade_entries_logged": len(trades),
    }
