# app/src/trading/execution/runtime_actions.py

from __future__ import annotations

import time
from typing import ClassVar, Protocol

from src.core.event_bus import EventBus
from src.core.numbers import safe_float
from src.core.types import JsonDict
from src.trading.auto.state import AutoTradeState
from src.trading.execution.models import ExecutionDecision
from src.trading.execution.payloads import (
    build_autonomous_payload,
    build_execution_quality_payload,
    build_full_position_intelligence_payload,
    build_market_context_payload,
    build_position_health_payload,
    build_runtime_payload,
    build_runtime_protection_payload,
)
from src.trading.journal.service import JournalService
from src.trading.position.state import PositionState
from src.trading.execution.constants import (
    AUTONOMOUS_ACTION_EXIT,
    AUTONOMOUS_ACTION_EXIT_BLOCKED,
    AUTONOMOUS_ACTION_HOLD,
    AUTONOMOUS_ACTION_PROTECT,
    AUTONOMOUS_ACTION_REDUCE,
    AUTONOMOUS_ACTION_WATCH,
    AUTO_STATUS_RUNNING,
    EXECUTION_ACTION_NONE,
    EXECUTION_REASON_AUTONOMOUS_EXIT,
    EXECUTION_TYPE_RUNTIME_ACTION,
    POSITION_SIDE_NONE,
    RUNTIME_ACTION_COOLDOWN,
    RUNTIME_ACTION_COOLDOWN_SECONDS,
    RUNTIME_ACTION_SKIPPED,
    RUNTIME_ACTION_UNKNOWN,
    RUNTIME_EXIT_CONFIDENCE_THRESHOLD,
    get_position_exit_thresholds,
)


class _ExecutionRuntimeActionsProtocol(Protocol):
    _position: ClassVar[PositionState]

    def _sync_state_from_position(
        self,
        state: AutoTradeState,
    ) -> None:
        ...

    def _close_position(
        self,
        state: AutoTradeState,
        *,
        forced_reason: str | None = None,
    ) -> ExecutionDecision:
        ...

    def _force_runtime_protect(
        self,
        state: AutoTradeState,
        *,
        reason: str,
    ) -> bool:
        ...


class ExecutionRuntimeActionsMixin(_ExecutionRuntimeActionsProtocol):
    # ----- Runtime autonomous actions subsystem.
    # Отвечает за:
    # - autonomous EXIT;
    # - autonomous PROTECT;
    # - autonomous REDUCE;
    # - cooldown runtime действий;
    # - runtime logging.
    #
    # Важно:
    # На текущем этапе PROTECT и REDUCE пока НЕ исполняют реальное действие.
    # Они логируются как диагностические runtime-сигналы.
    # Реальное закрытие позиции сейчас делает только AUTONOMOUS_ACTION_EXIT.

    _runtime_action_cooldown_seconds = RUNTIME_ACTION_COOLDOWN_SECONDS
    _last_runtime_action_key: str | None = None

    # ----- PUBLIC -----
    def process_runtime_action(
        self,
        state: AutoTradeState,
    ) -> ExecutionDecision:
        # Главный runtime action processor.
        #
        # Этот метод вызывается после основного engine.process().
        # Если позиция открыта и autonomous_management выставил EXIT,
        # здесь позиция может быть реально закрыта.

        self._sync_state_from_position(state)

        position = type(self)._position

        if state.status != AUTO_STATUS_RUNNING:
            reason = "Runtime action доступен только в режиме RUNNING."
            state.last_execution_action = RUNTIME_ACTION_SKIPPED
            state.last_execution_reason = reason
            return ExecutionDecision(EXECUTION_ACTION_NONE, False, reason)

        if position.side == POSITION_SIDE_NONE:
            reason = "Нет открытой позиции для runtime action."
            state.last_execution_action = RUNTIME_ACTION_SKIPPED
            state.last_execution_reason = reason
            return ExecutionDecision(EXECUTION_ACTION_NONE, False, reason)

        action = str(
            getattr(state, "autonomous_action", "") or ""
        ).upper()

        confidence = safe_float(
            getattr(state, "autonomous_action_confidence", None)
        ) or 0.0

        reason = str(
            getattr(state, "autonomous_action_reason", "") or ""
        )

        if action in {"", AUTONOMOUS_ACTION_HOLD, AUTONOMOUS_ACTION_WATCH}:
            skip_reason = "Runtime action не требуется."
            state.last_execution_action = RUNTIME_ACTION_SKIPPED
            state.last_execution_reason = skip_reason
            return ExecutionDecision(EXECUTION_ACTION_NONE, False, skip_reason)

        if self._runtime_action_cooldown_active(state, action):
            skip_reason = "Runtime action cooldown активен."
            state.last_execution_action = RUNTIME_ACTION_COOLDOWN
            state.last_execution_reason = skip_reason
            return ExecutionDecision(EXECUTION_ACTION_NONE, False, skip_reason)

        if action == AUTONOMOUS_ACTION_PROTECT:
            protect_reason = reason or "позиция требует защиты"

            # Теперь PROTECT — это не только лог.
            # Если позиция уже в плюсе, protection layer принудительно включает
            # break-even и при достаточной прибыли подтягивает profit-lock.
            protected = self._force_runtime_protect(
                state,
                reason=protect_reason,
            )

            return self._log_runtime_action(
                state=state,
                action=AUTONOMOUS_ACTION_PROTECT,
                reason=(
                    protect_reason
                    if protected
                    else f"{protect_reason}; protection не применён"
                ),
                confidence=confidence,
                executed=protected,
            )

        if action == AUTONOMOUS_ACTION_REDUCE:
            # Пока REDUCE только логируется.
            # Если partial close не реализован, лучше позже перевести REDUCE
            # в PROTECT или EXIT, чтобы не было иллюзии действия.
            return self._log_runtime_action(
                state=state,
                action=AUTONOMOUS_ACTION_REDUCE,
                reason=reason or "позиция требует уменьшения",
                confidence=confidence,
                executed=False,
            )

        if action == AUTONOMOUS_ACTION_EXIT:
            early_guard_reason = self._early_exit_guard_block_reason(state)

            if early_guard_reason is not None:
                return self._log_runtime_action(
                    state=state,
                    action=AUTONOMOUS_ACTION_EXIT_BLOCKED,
                    reason=early_guard_reason,
                    confidence=confidence,
                    executed=False,
                    cooldown_action=None,
                )

            if confidence < RUNTIME_EXIT_CONFIDENCE_THRESHOLD:
                return self._log_runtime_action(
                    state=state,
                    action=AUTONOMOUS_ACTION_EXIT_BLOCKED,
                    reason=(
                        "autonomous exit заблокирован: "
                        f"confidence {confidence:.2f} < "
                        f"{RUNTIME_EXIT_CONFIDENCE_THRESHOLD:.2f}"
                    ),
                    confidence=confidence,
                    executed=False,
                    cooldown_action=None,
                )

            self._log_runtime_action(
                state=state,
                action=AUTONOMOUS_ACTION_EXIT,
                reason=reason or "autonomous exit",
                confidence=confidence,
                executed=True,
            )

            decision = self._close_position(
                state,
                forced_reason=EXECUTION_REASON_AUTONOMOUS_EXIT,
            )

            state.autonomous_last_action = AUTONOMOUS_ACTION_EXIT
            state.autonomous_last_action_reason = reason or decision.reason
            state.autonomous_last_action_at = time.monotonic()

            return decision

        unknown_reason = f"Неизвестный runtime action: {action}."
        state.last_execution_action = RUNTIME_ACTION_UNKNOWN
        state.last_execution_reason = unknown_reason

        return ExecutionDecision(EXECUTION_ACTION_NONE, False, unknown_reason)

    # ----- COOLDOWN -----
    def _runtime_action_cooldown_active(
        self,
        state: AutoTradeState,
        action: str,
    ) -> bool:
        # Проверка cooldown runtime action.
        # Cooldown нужен, чтобы один и тот же runtime action не спамил
        # журнал и EventBus на каждом цикле.

        ts = safe_float(
            getattr(state, "autonomous_last_action_at", None)
        )

        last_action = str(
            getattr(state, "autonomous_last_action", "") or ""
        ).upper()

        if ts is None:
            return False

        if last_action != action:
            return False

        return (
            time.monotonic() - ts
        ) < self._runtime_action_cooldown_seconds

    # ----- PAYLOAD -----
    def _build_runtime_action_payload(
        self,
        *,
        state: AutoTradeState,
        position: PositionState,
        trade_id: str | None,
        action: str,
        reason: str,
        confidence: float,
        executed: bool,
    ) -> JsonDict:
        return {
            # ---------- Trade ----------
            "trade_id": trade_id,
            "trade_sequence": position.trade_sequence,
            "trade_cycle_number": position.trade_cycle_number,

            # ---------- Event ----------
            "execution_type": EXECUTION_TYPE_RUNTIME_ACTION,
            "action": action,
            "executed": executed,
            "reason": reason,
            "confidence": confidence,

            # ---------- Runtime ----------
            **build_runtime_payload(state),

            # ---------- Instrument / Position ----------
            "symbol": state.symbol,
            "position_side": position.side,
            "entry_price": position.entry_price,
            "size": position.size,
            "leverage": position.leverage,
            "unrealized_pnl_usd": state.unrealized_pnl_usd,
            "position_pnl_percent": state.position_pnl_percent,

            # ---------- Health / intelligence ----------
            **build_position_health_payload(state),
            "position_pressure": state.position_pressure,
            "position_exit_pressure": state.position_exit_pressure,

            **build_full_position_intelligence_payload(state),

            # ---------- Autonomous ----------
            **build_autonomous_payload(state),
            "autonomous_last_action": state.autonomous_last_action,
            "autonomous_last_action_reason": state.autonomous_last_action_reason,

            # ---------- Protection / market / execution ----------
            **build_runtime_protection_payload(state),
            **build_market_context_payload(state),
            **build_execution_quality_payload(state),
        }

    # ----- LOGGING -----
    def _log_runtime_action(
        self,
        *,
        state: AutoTradeState,
        action: str,
        reason: str,
        confidence: float,
        executed: bool,
        cooldown_action: str | None = None,
    ) -> ExecutionDecision:
        # Runtime action logging + deduplication.
        # Даже если действие не исполняется, payload помогает понять:
        # - почему runtime action появился;
        # - почему он был заблокирован;
        # - какие были position health / semantics / market context.

        position = type(self)._position
        trade_id = position.trade_id or state.current_trade_id

        key = (
            f"{trade_id}:"
            f"{state.symbol}:"
            f"{position.side}:"
            f"{action}:"
            f"{reason}:"
            f"{confidence:.2f}"
        )

        if key != type(self)._last_runtime_action_key:
            type(self)._last_runtime_action_key = key

            payload = self._build_runtime_action_payload(
                state=state,
                position=position,
                trade_id=trade_id,
                action=action,
                reason=reason,
                confidence=confidence,
                executed=executed,
            )

            JournalService().log_ui_warning(
                event_type="runtime_position_action",
                message=(
                    f"Runtime action: {action}. "
                    f"Причина: {reason}."
                ),
                screen="auto",
                action="runtime_position_action",
                payload=payload,
            )

            EventBus.emit(
                "runtime_position_action",
                payload,
            )

        state.last_execution_action = action
        state.last_execution_reason = reason
        state.autonomous_last_action = cooldown_action or action
        state.autonomous_last_action_reason = reason
        state.autonomous_last_action_at = time.monotonic()

        return ExecutionDecision(
            action,
            executed,
            reason,
        )

    # ----- EARLY EXIT GUARD -----
    def _early_exit_guard_block_reason(self, state: AutoTradeState) -> str | None:
        # Early exit guard защищает от слишком раннего закрытия позиции
        # на обычном шуме/спреде/первой волне после входа.
        #
        # Но раньше он блокировал выход почти всегда до min_hold,
        # пока убыток не доходил до hard_loss.
        #
        # Новая логика:
        # - обычный ранний шум всё ещё блокируется;
        # - реальное ухудшение позиции guard больше НЕ блокирует.

        hold_seconds = safe_float(getattr(state, "position_hold_seconds", None))
        pnl_percent = safe_float(getattr(state, "position_pnl_percent", None))

        if hold_seconds is None or pnl_percent is None:
            return None

        thresholds = get_position_exit_thresholds(
            getattr(state, "symbol", None)
        )

        min_hold = thresholds["min_hold"]
        hard_loss = thresholds["hard_loss"]

        if hold_seconds >= min_hold:
            return None

        # Если просадка уже критическая — guard не мешает защите.
        if pnl_percent <= hard_loss:
            return None

        bypass_reason = self._early_exit_guard_bypass_reason(state)

        if bypass_reason is not None:
            return None

        return (
            "early exit guard: позиция ещё слишком новая для закрытия "
            f"({hold_seconds:.0f}s < {min_hold:.0f}s)"
        )

    def _early_exit_guard_bypass_reason(self, state: AutoTradeState) -> str | None:
        # Причины, при которых ранний выход нужно разрешить.
        # Это не делает выход автоматическим само по себе:
        # action всё равно должен быть AUTONOMOUS_ACTION_EXIT,
        # а confidence должен пройти RUNTIME_EXIT_CONFIDENCE_THRESHOLD.

        adverse_momentum = bool(
            getattr(state, "position_adverse_momentum", False)
        )

        trend_alignment = str(
            getattr(state, "position_trend_alignment", "") or ""
        ).upper()

        risk_level = str(
            getattr(state, "position_risk_level", "") or ""
        ).upper()

        conviction_state = str(
            getattr(state, "position_conviction_state", "") or ""
        ).upper()

        stall_state = str(
            getattr(state, "position_stall_state", "") or ""
        ).upper()

        exit_urgency = str(
            getattr(state, "position_exit_urgency", "") or ""
        ).upper()

        decay_state = str(
            getattr(state, "position_decay_state", "") or ""
        ).upper()

        if risk_level in {"HIGH", "ELEVATED"}:
            return f"early exit allowed: position risk is {risk_level}"

        if adverse_momentum and trend_alignment == "AGAINST":
            return "early exit allowed: trend and momentum are against position"

        if conviction_state == "BROKEN":
            return "early exit allowed: position conviction is broken"

        if stall_state == "ADVERSE_STALLED":
            return "early exit allowed: position is adverse stalled"

        if exit_urgency in {"IMMEDIATE", "HIGH"}:
            return f"early exit allowed: exit urgency is {exit_urgency}"

        if decay_state in {"ACCELERATING_LOSS", "CONTEXT_DECAY"}:
            return f"early exit allowed: position decay is {decay_state}"

        return None

    # Старый публичный helper оставляем для совместимости,
    # если где-то ещё в коде он вызывается напрямую.
    def _early_exit_guard_active(self, state: AutoTradeState) -> bool:
        return self._early_exit_guard_block_reason(state) is not None