from __future__ import annotations

from dataclasses import dataclass, asdict
from datetime import datetime, timezone
from decimal import Decimal, ROUND_HALF_UP
from typing import Any, Literal

DeskAction = Literal["no_trade", "watch_shadow", "tiny_live_candidate"]
DeskDirection = Literal["long", "short", "none"]

HARD_BLOCKERS = {
    "stale_data",
    "low_reliability",
    "weak_liquidity",
    "spread_unknown",
    "spread_too_wide",
    "l2_unavailable",
    "impact_unknown",
    "impact_slippage_too_high",
    "volatility_too_high",
    "risk_score_too_low",
    "confluence_below_threshold",
}


def D(value: Any, default: str = "0") -> Decimal:
    try:
        return Decimal(str(value))
    except Exception:
        return Decimal(default)


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


@dataclass(frozen=True)
class DeskMandate:
    mode: str = "shadow_first"
    long_only_tiny_live: bool = True
    min_watch_score: Decimal = Decimal("70")
    min_tiny_live_score: Decimal = Decimal("80")
    max_spread_pct: Decimal = Decimal("0.08")
    max_total_open_notional_usdc: Decimal = Decimal("45")
    max_notional_per_trade_usdc: Decimal = Decimal("15")
    max_open_trades: int = 3
    allowed_tiny_live_coins: tuple[str, ...] = ("BTC", "ETH", "SOL", "LINK")
    live_order_allowed: bool = False
    mainnet_signed_action: bool = False

    def to_dict(self) -> dict[str, Any]:
        raw = asdict(self)
        for key, value in list(raw.items()):
            if isinstance(value, Decimal):
                raw[key] = str(value)
        raw["allowed_tiny_live_coins"] = list(self.allowed_tiny_live_coins)
        return raw


@dataclass(frozen=True)
class TradePlan:
    coin: str
    direction: DeskDirection
    action: DeskAction
    score: Decimal
    confidence: str
    setup: str
    entry_zone: dict[str, str]
    stop_loss: str | None
    take_profit: dict[str, str]
    risk_reward: str
    notional_cap_usdc: str
    reasons: tuple[str, ...]
    blockers: tuple[str, ...]
    evidence: dict[str, Any]
    requires_preflight: bool = True
    live_order_allowed: bool = False
    mainnet_signed_action: bool = False

    def to_dict(self) -> dict[str, Any]:
        raw = asdict(self)
        raw["score"] = q(self.score)
        raw["reasons"] = list(self.reasons)
        raw["blockers"] = list(self.blockers)
        return raw


@dataclass(frozen=True)
class TraderDeskReport:
    schema_version: str
    timestamp: str
    mode: str
    market_regime: str
    plans: tuple[TradePlan, ...]
    summary: dict[str, Any]
    mandate: DeskMandate
    live_order_allowed: bool = False
    mainnet_signed_action: bool = False

    def to_dict(self) -> dict[str, Any]:
        return {
            "schema_version": self.schema_version,
            "timestamp": self.timestamp,
            "mode": self.mode,
            "market_regime": self.market_regime,
            "live_order_allowed": self.live_order_allowed,
            "mainnet_signed_action": self.mainnet_signed_action,
            "mandate": self.mandate.to_dict(),
            "summary": self.summary,
            "plans": [plan.to_dict() for plan in self.plans],
        }


def _unique(items: list[str]) -> tuple[str, ...]:
    return tuple(dict.fromkeys(x for x in items if x))


def _coin_snapshot(payload: dict[str, Any], coin: str) -> dict[str, Any]:
    return (((payload.get("market_snapshot") or {}).get("coins") or {}).get(coin) or {})


def _confluence_row(payload: dict[str, Any], coin: str) -> dict[str, Any]:
    return (((payload.get("confluence") or {}).get("scores") or {}).get(coin) or {})


def _sentiment_row(payload: dict[str, Any], coin: str) -> dict[str, Any]:
    return (_coin_snapshot(payload, coin).get("sentiment") or {})


def _fundamentals_row(payload: dict[str, Any], coin: str) -> dict[str, Any]:
    return (_coin_snapshot(payload, coin).get("fundamentals") or {})


def _derive_direction(coin_snap: dict[str, Any], market_regime: str) -> DeskDirection:
    trend_state = str((coin_snap.get("trend") or {}).get("state") or "neutral")
    if market_regime in {"risk_on", "selective"} and trend_state == "bullish":
        return "long"
    if market_regime == "risk_off" and trend_state == "bearish":
        return "short"
    return "none"


def _community_adjustment(sentiment: dict[str, Any]) -> tuple[Decimal, list[str]]:
    reasons: list[str] = []
    if sentiment.get("research_only") is not True:
        return Decimal("0"), ["community_context_not_loaded"]
    track_raw = sentiment.get("track_record")
    track: dict[str, Any] = track_raw if isinstance(track_raw, dict) else {}
    evaluated = int(track.get("evaluated") or 0)
    hitrate = D(track.get("hitrate_pct"))
    if evaluated >= 10 and hitrate >= Decimal("58"):
        reasons.append("community_track_record_supportive_research_only")
        return Decimal("3"), reasons
    if evaluated >= 10 and hitrate <= Decimal("42"):
        reasons.append("community_track_record_contrarian_warning")
        return Decimal("-4"), reasons
    ideas = int(sentiment.get("recent_ideas_count") or 0)
    if ideas:
        reasons.append("community_ideas_present_research_only")
    else:
        reasons.append("no_recent_community_context")
    return Decimal("0"), reasons


def _fundamental_adjustment(fundamentals: dict[str, Any]) -> tuple[Decimal, list[str]]:
    status = str(fundamentals.get("status") or "not_loaded")
    if status in {"loaded", "ok", "available"}:
        return Decimal("2"), ["coingecko_fundamentals_loaded"]
    if status == "not_loaded":
        return Decimal("0"), ["fundamentals_neutral_not_loaded"]
    return Decimal("-1"), [f"fundamentals_status_{status}"]


def _plan_prices(direction: DeskDirection, mid: Decimal, atr_pct: Decimal) -> tuple[dict[str, str], str | None, dict[str, str], str]:
    if direction == "none" or mid <= 0:
        return {}, None, {}, "0.00"
    risk_pct = max(Decimal("0.80"), min(Decimal("3.00"), atr_pct * Decimal("1.25")))
    retest_pct = max(Decimal("0.20"), min(Decimal("1.20"), atr_pct * Decimal("0.35")))
    if direction == "long":
        entry_low = mid * (Decimal("1") - retest_pct / Decimal("100"))
        entry_high = mid * (Decimal("1") + min(Decimal("0.15"), atr_pct * Decimal("0.08")) / Decimal("100"))
        stop = mid * (Decimal("1") - risk_pct / Decimal("100"))
        tp1 = mid * (Decimal("1") + risk_pct * Decimal("1.5") / Decimal("100"))
        tp2 = mid * (Decimal("1") + risk_pct * Decimal("2.4") / Decimal("100"))
    else:
        entry_low = mid * (Decimal("1") - min(Decimal("0.15"), atr_pct * Decimal("0.08")) / Decimal("100"))
        entry_high = mid * (Decimal("1") + retest_pct / Decimal("100"))
        stop = mid * (Decimal("1") + risk_pct / Decimal("100"))
        tp1 = mid * (Decimal("1") - risk_pct * Decimal("1.5") / Decimal("100"))
        tp2 = mid * (Decimal("1") - risk_pct * Decimal("2.4") / Decimal("100"))
    return (
        {"low": q(entry_low, "0.0001"), "high": q(entry_high, "0.0001"), "style": "retest_limit_zone"},
        q(stop, "0.0001"),
        {"tp1": q(tp1, "0.0001"), "tp2": q(tp2, "0.0001"), "management": "break_even_then_profit_lock_then_trailing"},
        q(Decimal("1.50")),
    )


def build_trade_plan(payload: dict[str, Any], coin: str, mandate: DeskMandate = DeskMandate()) -> TradePlan:
    coin = coin.upper()
    coin_snap = _coin_snapshot(payload, coin)
    confluence = _confluence_row(payload, coin)
    sentiment = _sentiment_row(payload, coin)
    fundamentals = _fundamentals_row(payload, coin)
    market_regime = str((payload.get("confluence") or {}).get("market_regime") or (payload.get("market_snapshot") or {}).get("market_regime") or "unknown")

    base_score = D(confluence.get("final_trade_score") or confluence.get("final_score"))
    community_adj, community_reasons = _community_adjustment(sentiment)
    fundamental_adj, fundamental_reasons = _fundamental_adjustment(fundamentals)
    score = max(Decimal("0"), min(Decimal("100"), base_score + community_adj + fundamental_adj))

    risk_blockers = list(((coin_snap.get("risk") or {}).get("blockers") or []))
    confluence_blockers = list(confluence.get("blockers") or [])
    blockers = _unique(risk_blockers + confluence_blockers)
    hard = sorted(set(blockers) & HARD_BLOCKERS)
    direction = _derive_direction(coin_snap, market_regime)
    reasons: list[str] = []
    reasons.extend(community_reasons)
    reasons.extend(fundamental_reasons)

    price = D((coin_snap.get("price") or {}).get("mid"))
    atr_pct = D((coin_snap.get("volatility") or {}).get("atr_pct"))
    spread_pct = D((coin_snap.get("liquidity") or {}).get("spread_pct"), "99")

    if direction == "none":
        reasons.append("no_clear_direction_from_regime_and_trend")
    if direction == "short" and mandate.long_only_tiny_live:
        reasons.append("short_is_shadow_only_under_tiny_live_mandate")
    if spread_pct > mandate.max_spread_pct:
        hard.append("spread_above_mandate")

    if hard or direction == "none" or score < mandate.min_watch_score:
        action: DeskAction = "no_trade"
        confidence = "blocked"
    elif direction == "short" and mandate.long_only_tiny_live:
        action = "watch_shadow"
        confidence = "shadow_only"
    elif score >= mandate.min_tiny_live_score and coin in mandate.allowed_tiny_live_coins:
        action = "tiny_live_candidate"
        confidence = "candidate_requires_final_preflight"
    else:
        action = "watch_shadow"
        confidence = "watchlist"

    entry_zone, stop_loss, take_profit, rr = _plan_prices(direction, price, atr_pct)
    setup = "trend_retest_continuation" if direction == "long" else "risk_off_trend_retest_short" if direction == "short" else "no_trade"
    evidence = {
        "market_regime": market_regime,
        "confluence_score": str(base_score),
        "desk_score_adjusted": q(score),
        "trend_state": (coin_snap.get("trend") or {}).get("state"),
        "momentum_state": (coin_snap.get("momentum") or {}).get("state"),
        "volatility_state": (coin_snap.get("volatility") or {}).get("state"),
        "liquidity_state": (coin_snap.get("liquidity") or {}).get("state"),
        "spread_pct": str(spread_pct),
        "atr_pct": str(atr_pct),
        "funding": (coin_snap.get("derivatives") or {}).get("funding"),
        "crowding_state": (coin_snap.get("derivatives") or {}).get("crowding_state"),
        "sentiment_source": sentiment.get("source"),
        "fundamentals_source": fundamentals.get("source"),
    }
    return TradePlan(
        coin=coin,
        direction=direction,
        action=action,
        score=score,
        confidence=confidence,
        setup=setup,
        entry_zone=entry_zone,
        stop_loss=stop_loss,
        take_profit=take_profit,
        risk_reward=rr,
        notional_cap_usdc=str(mandate.max_notional_per_trade_usdc if action == "tiny_live_candidate" else Decimal("0")),
        reasons=_unique(reasons),
        blockers=_unique(list(blockers) + hard),
        evidence=evidence,
        requires_preflight=action == "tiny_live_candidate",
        live_order_allowed=False,
        mainnet_signed_action=False,
    )


def build_trader_desk_report(payload: dict[str, Any], *, mandate: DeskMandate = DeskMandate(), max_plans: int = 8) -> TraderDeskReport:
    coins = sorted((((payload.get("market_snapshot") or {}).get("coins") or {}).keys()))
    plans = [build_trade_plan(payload, coin, mandate) for coin in coins]
    plans.sort(key=lambda p: (p.action == "tiny_live_candidate", p.action == "watch_shadow", p.score), reverse=True)
    top = tuple(plans[:max_plans])
    summary = {
        "tiny_live_candidates": sum(1 for p in plans if p.action == "tiny_live_candidate"),
        "watch_shadow": sum(1 for p in plans if p.action == "watch_shadow"),
        "blocked": sum(1 for p in plans if p.action == "no_trade"),
        "long_plans": sum(1 for p in plans if p.direction == "long"),
        "short_plans": sum(1 for p in plans if p.direction == "short"),
        "none_direction_plans": sum(1 for p in plans if p.direction == "none"),
        "short_share_pct": q((Decimal(sum(1 for p in plans if p.direction == "short")) / Decimal(len(plans)) * Decimal("100")) if plans else Decimal("0")),
        "best_coin": top[0].coin if top else None,
        "best_action": top[0].action if top else None,
        "best_score": q(top[0].score) if top else "0.00",
        "next_step": "run_final_preflight_before_any_live_order" if any(p.action == "tiny_live_candidate" for p in plans) else "keep_shadow_watch_and_collect_evidence",
    }
    return TraderDeskReport(
        schema_version="trader_desk.v1",
        timestamp=datetime.now(timezone.utc).isoformat(),
        mode="read_only_professional_trader_desk",
        market_regime=str((payload.get("confluence") or {}).get("market_regime") or "unknown"),
        plans=top,
        summary=summary,
        mandate=mandate,
        live_order_allowed=False,
        mainnet_signed_action=False,
    )


def format_trader_desk_report(report: TraderDeskReport) -> str:
    lines = [
        "JARVIS Trader Desk v1:",
        f"mode={report.mode}, regime={report.market_regime}, live=nein, signed=nein",
        f"summary: tiny={report.summary['tiny_live_candidates']}, shadow={report.summary['watch_shadow']}, blocked={report.summary['blocked']}, next={report.summary['next_step']}",
    ]
    for plan in report.plans:
        lines.append(
            f"- {plan.coin}: {plan.action}, {plan.direction}, score={q(plan.score)}, setup={plan.setup}, "
            f"entry={plan.entry_zone or '-'}, stop={plan.stop_loss or '-'}, tp={plan.take_profit or '-'}, blockers={list(plan.blockers) or 'keine'}"
        )
    return "\n".join(lines)
