from __future__ import annotations

from decimal import Decimal
from typing import Any

MAKER_FEE_RATE = Decimal("0.00015")  # explicit research assumption; configurable before live use
TAKER_FEE_RATE = Decimal("0.00045")
MIN_EXIT_SLIPPAGE_PCT = Decimal("0.02")


def _d(value: Any) -> Decimal:
    return Decimal(str(value))


def _base(event: str, **values: Any) -> dict[str, Any]:
    return {"event": event, **values, "paper_trading": True, "research_only": True, "live_order_allowed": False, "mainnet_signed_action": False}


def submit_shadow(state: dict[str, Any], *, coin: str, side: str, feature: Any, size: Decimal, stop_loss: Decimal, take_profit: Decimal, source_window_id: str, strategy_id: str, max_wait_bars: int = 2, max_hold_bars: int = 16) -> dict[str, Any]:
    pending = state.setdefault("pending", {})
    state.setdefault("open", {})
    if coin in pending or coin in state["open"]:
        return _base("maker_rejected", coin=coin, reason="shadow_order_already_exists")
    mid = _d(feature.current_price)
    half_spread = _d(feature.spread_pct) / Decimal("2")
    limit = mid * (Decimal("1") - half_spread / Decimal("100")) if side == "long" else mid * (Decimal("1") + half_spread / Decimal("100"))
    order = {
        "coin": coin,
        "side": side,
        "limit_price": str(limit),
        "size": str(size),
        "stop_loss": str(stop_loss),
        "take_profit": str(take_profit),
        "submitted_candle_ts": int(feature.candle_ts),
        "last_candle_ts": int(feature.candle_ts),
        "wait_bars": 0,
        "max_wait_bars": int(max_wait_bars),
        "max_hold_bars": int(max_hold_bars),
        "funding_rate_hourly_pct": str(getattr(feature, "funding_rate_hourly_pct", Decimal("0"))),
        "source_window_id": source_window_id,
        "strategy_id": strategy_id,
    }
    pending[coin] = order
    return _base("maker_pending", **order, entry_order_type="limit_Alo")


def advance_shadow(state: dict[str, Any], features: dict[str, Any]) -> list[dict[str, Any]]:
    pending = state.setdefault("pending", {})
    opened = state.setdefault("open", {})
    events: list[dict[str, Any]] = []
    for coin, order in list(pending.items()):
        feature = features.get(coin)
        if feature is None or int(feature.candle_ts) <= int(order["last_candle_ts"]):
            continue
        order["last_candle_ts"] = int(feature.candle_ts)
        order["wait_bars"] = int(order["wait_bars"]) + 1
        limit = _d(order["limit_price"])
        touched = _d(feature.low_15m) <= limit if order["side"] == "long" else _d(feature.high_15m) >= limit
        if touched:
            entry_fee = limit * _d(order["size"]) * MAKER_FEE_RATE
            opened[coin] = {**order, "entry_price": str(limit), "maker_entry_fee_usd": str(entry_fee), "fill_candle_ts": int(feature.candle_ts), "last_candle_ts": int(feature.candle_ts), "bars_held": 0}
            del pending[coin]
            events.append(_base("maker_fill", **opened[coin], entry_order_type="limit_Alo"))
        elif int(order["wait_bars"]) >= int(order["max_wait_bars"]):
            del pending[coin]
            events.append(_base("maker_cancel", **order, reason="not_filled_before_expiry", entry_order_type="limit_Alo"))
    for coin, pos in list(opened.items()):
        feature = features.get(coin)
        if feature is None or int(feature.candle_ts) <= int(pos["last_candle_ts"]):
            continue
        pos["last_candle_ts"] = int(feature.candle_ts)
        pos["bars_held"] = int(pos["bars_held"]) + 1
        side = str(pos["side"])
        stop, target = _d(pos["stop_loss"]), _d(pos["take_profit"])
        stop_hit = _d(feature.low_15m) <= stop if side == "long" else _d(feature.high_15m) >= stop
        target_hit = _d(feature.high_15m) >= target if side == "long" else _d(feature.low_15m) <= target
        time_exit = int(pos["bars_held"]) >= int(pos["max_hold_bars"])
        if not stop_hit and not target_hit and not time_exit:
            continue
        reason = "stop_loss" if stop_hit else "take_profit" if target_hit else "time_exit"
        raw_exit = stop if stop_hit else target if target_hit else _d(feature.close_15m)
        impact = _d(feature.sell_impact_1k_pct if side == "long" else feature.buy_impact_1k_pct)
        slip_pct = max(MIN_EXIT_SLIPPAGE_PCT, _d(feature.spread_pct) / Decimal("2"), impact)
        exit_price = raw_exit * (Decimal("1") - slip_pct / Decimal("100")) if side == "long" else raw_exit * (Decimal("1") + slip_pct / Decimal("100"))
        size, entry = _d(pos["size"]), _d(pos["entry_price"])
        gross = (exit_price - entry) * size if side == "long" else (entry - exit_price) * size
        entry_fee = _d(pos["maker_entry_fee_usd"])
        exit_fee = exit_price * size * TAKER_FEE_RATE
        funding = entry * size * _d(pos.get("funding_rate_hourly_pct")) / Decimal("100") * Decimal(int(pos["bars_held"])) * Decimal("0.25")
        if side == "short":
            funding = -funding
        net = gross - entry_fee - exit_fee - funding
        events.append(_base("maker_exit", **pos, exit_price=str(exit_price), exit_reason=reason, gross_pnl_usd=str(gross), taker_exit_fee_usd=str(exit_fee), funding_cost_usd=str(funding), net_pnl_usd=str(net), entry_order_type="limit_Alo", exit_order_type="market_taker"))
        del opened[coin]
    return events
