from __future__ import annotations

from dataclasses import dataclass
from decimal import Decimal, ROUND_HALF_UP
from typing import Any

from src.market.context import CoinMarketContext, MarketContextSnapshot
from src.market.confluence import MarketConfluenceReport


def _q(value: Decimal, places: str = "0.0001") -> str:
    return str(value.quantize(Decimal(places), rounding=ROUND_HALF_UP))


def _pct(part: Decimal, whole: Decimal) -> Decimal:
    if whole == 0:
        return Decimal("0")
    return part / whole * Decimal("100")


@dataclass(frozen=True)
class NormalizedCoinSnapshot:
    """Phase-2 normalized per-coin market snapshot.

    This object is intentionally read-only and safe for reports/gates. Missing external
    data sources are represented explicitly with status fields instead of being guessed.
    """

    coin: str
    price: dict[str, Any]
    ohlcv: dict[str, Any]
    trend: dict[str, Any]
    momentum: dict[str, Any]
    volatility: dict[str, Any]
    liquidity: dict[str, Any]
    derivatives: dict[str, Any]
    fundamentals: dict[str, Any]
    sentiment: dict[str, Any]
    event_risk: dict[str, Any]
    regime: dict[str, Any]
    risk: dict[str, Any]
    confluence: dict[str, Any]
    live_order_allowed: bool = False
    mainnet_signed_action: bool = False

    def to_dict(self) -> dict[str, Any]:
        return {
            "coin": self.coin,
            "price": self.price,
            "ohlcv": self.ohlcv,
            "trend": self.trend,
            "momentum": self.momentum,
            "volatility": self.volatility,
            "liquidity": self.liquidity,
            "derivatives": self.derivatives,
            "fundamentals": self.fundamentals,
            "sentiment": self.sentiment,
            "event_risk": self.event_risk,
            "regime": self.regime,
            "risk": self.risk,
            "confluence": self.confluence,
            "live_order_allowed": self.live_order_allowed,
            "mainnet_signed_action": self.mainnet_signed_action,
        }


@dataclass(frozen=True)
class NormalizedMarketSnapshot:
    schema_version: str
    market_regime: str
    coins: dict[str, NormalizedCoinSnapshot]
    live_order_allowed: bool = False
    mainnet_signed_action: bool = False

    def to_dict(self) -> dict[str, Any]:
        return {
            "schema_version": self.schema_version,
            "market_regime": self.market_regime,
            "live_order_allowed": self.live_order_allowed,
            "mainnet_signed_action": self.mainnet_signed_action,
            "coins": {coin: snap.to_dict() for coin, snap in self.coins.items()},
        }


def build_normalized_coin_snapshot(ctx: CoinMarketContext, confluence_row: dict[str, Any], *, market_regime: str, fundamentals_row: dict[str, Any] | None = None, sentiment_row: dict[str, Any] | None = None, event_risk_row: dict[str, Any] | None = None) -> NormalizedCoinSnapshot:
    mid = ctx.mid if ctx.mid > 0 else ctx.mark_px
    atr_pct = _pct(ctx.atr, mid) if mid > 0 else Decimal("0")
    day_change_pct = _pct(mid - ctx.prev_day_px, ctx.prev_day_px) if ctx.prev_day_px > 0 and mid > 0 else Decimal("0")
    mark_oracle_basis_pct = _pct(ctx.mark_px - ctx.oracle_px, ctx.oracle_px) if ctx.oracle_px > 0 and ctx.mark_px > 0 else Decimal("0")

    trend_state = "bullish" if ctx.sma_fast > ctx.sma_slow and mid >= ctx.sma_fast else "bearish" if ctx.sma_fast < ctx.sma_slow else "neutral"
    momentum_state = "overheated" if ctx.rsi > Decimal("72") else "constructive" if ctx.rsi >= Decimal("45") else "weak"
    volatility_state = "tradeable" if Decimal("0.35") <= atr_pct <= Decimal("2.50") else "high" if atr_pct > Decimal("2.50") else "low"
    liquidity_state = "ok" if ctx.l2_available and ctx.spread_pct is not None and ctx.spread_pct <= Decimal("0.08") else "impaired"
    crowding_state = "crowded_long" if ctx.funding > Decimal("0.0001") or ctx.premium > Decimal("0.001") else "neutral"

    blockers = list(confluence_row.get("blockers") or [])
    if ctx.stale_data:
        blockers.append("stale_data")
    if ctx.reliability_score < Decimal("0.75"):
        blockers.append("low_reliability")

    return NormalizedCoinSnapshot(
        coin=ctx.coin.upper(),
        price={
            "mid": _q(mid),
            "mark": _q(ctx.mark_px),
            "oracle": _q(ctx.oracle_px),
            "prev_day": _q(ctx.prev_day_px),
            "day_change_pct": _q(day_change_pct),
        },
        ohlcv={
            "source": "hyperliquid_candleSnapshot",
            "interval": "collector_default_5m",
            "lookback": "collector_default_4h",
            "volume": _q(ctx.volume, "0.01"),
            "day_notional_volume": _q(ctx.day_ntl_vlm, "0.01"),
        },
        trend={
            "state": trend_state,
            "sma_fast": _q(ctx.sma_fast),
            "sma_slow": _q(ctx.sma_slow),
            "score": confluence_row.get("trend_score", "0.00"),
        },
        momentum={
            "state": momentum_state,
            "rsi": _q(ctx.rsi),
            "score": confluence_row.get("momentum_score", "0.00"),
        },
        volatility={
            "state": volatility_state,
            "atr": _q(ctx.atr),
            "atr_pct": _q(atr_pct),
            "score": confluence_row.get("volatility_score", "0.00"),
        },
        liquidity={
            "state": liquidity_state,
            "spread_pct": _q(ctx.spread_pct) if ctx.spread_pct is not None else "unknown",
            "l2_available": ctx.l2_available,
            "bid_depth_notional_5": _q(ctx.bid_depth_notional_5, "0.01"),
            "ask_depth_notional_5": _q(ctx.ask_depth_notional_5, "0.01"),
            "buy_impact_1k_pct": _q(ctx.buy_impact_1k_pct) if ctx.buy_impact_1k_pct is not None else "unknown",
            "sell_impact_1k_pct": _q(ctx.sell_impact_1k_pct) if ctx.sell_impact_1k_pct is not None else "unknown",
            "buy_impact_5k_pct": _q(ctx.buy_impact_5k_pct) if ctx.buy_impact_5k_pct is not None else "unknown",
            "sell_impact_5k_pct": _q(ctx.sell_impact_5k_pct) if ctx.sell_impact_5k_pct is not None else "unknown",
            "score": confluence_row.get("liquidity_score", "0.00"),
        },
        derivatives={
            "source": "hyperliquid_metaAndAssetCtxs",
            "funding": _q(ctx.funding, "0.00000001"),
            "premium": _q(ctx.premium, "0.00000001"),
            "open_interest": _q(ctx.open_interest, "0.01"),
            "mark_oracle_basis_pct": _q(mark_oracle_basis_pct),
            "crowding_state": crowding_state,
            "score": confluence_row.get("derivatives_score", "0.00"),
        },
        fundamentals=fundamentals_row or {"source": "coingecko_markets", "status": "not_loaded"},
        sentiment=sentiment_row or {
            "source": "pending_news_and_tradingview_track_record",
            "status": "not_loaded",
            "score": confluence_row.get("sentiment_score", "50.00"),
        },
        event_risk=event_risk_row or {
            "source": "news_search_research_context",
            "status": "not_loaded",
            "risk_level": "unknown",
            "score": confluence_row.get("event_risk_score", "70.00"),
            "research_only": True,
            "live_order_allowed": False,
            "mainnet_signed_action": False,
        },
        regime={
            "market_regime": market_regime,
            "score": confluence_row.get("regime_score", "0.00"),
        },
        risk={
            "reliability_score": _q(ctx.reliability_score),
            "stale_data": ctx.stale_data,
            "score": confluence_row.get("risk_score", "0.00"),
            "blockers": list(dict.fromkeys(blockers)),
        },
        confluence={
            "score_version": confluence_row.get("score_version", "confluence_score.v1"),
            "final_trade_score": confluence_row.get("final_trade_score", confluence_row.get("final_score", "0.00")),
            "final_score": confluence_row.get("final_score", "0.00"),
            "recommendation": confluence_row.get("recommendation", "block_new_entry"),
            "blockers": confluence_row.get("blockers", []),
        },
    )


def build_normalized_market_snapshot(snapshot: MarketContextSnapshot, confluence: MarketConfluenceReport, *, fundamentals: dict[str, Any] | None = None, sentiment: dict[str, Any] | None = None, event_risk: dict[str, Any] | None = None) -> NormalizedMarketSnapshot:
    confluence_dict = confluence.to_dict()
    fundamentals = fundamentals or {}
    sentiment = sentiment or {}
    event_risk = event_risk or {}
    coins = {
        coin: build_normalized_coin_snapshot(ctx, confluence_dict["scores"].get(coin, {}), market_regime=confluence.market_regime, fundamentals_row=fundamentals.get(coin), sentiment_row=sentiment.get(coin), event_risk_row=event_risk.get(coin))
        for coin, ctx in sorted(snapshot.contexts.items())
    }
    return NormalizedMarketSnapshot(schema_version="market_snapshot.v1", market_regime=confluence.market_regime, coins=coins)
