# app/src/trading/execution/position_protection.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, NumericLike
from src.trading.auto.state import AutoTradeState
from src.trading.execution.models import ExecutionDecision
from src.trading.execution.payloads import (
    build_execution_price_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.execution.position_metrics import PositionMetrics, build_position_metrics
from src.trading.execution.pricing import ExecutionPrice
from src.trading.journal.service import JournalService
from src.trading.position.state import PositionState


PROTECTION_THRESHOLDS_BY_ASSET = {
    "BTC": {
        "break_even_activate": 0.45,
        "break_even_buffer": 0.12,
        "profit_lock_activate": 0.95,
        "profit_lock_distance": 0.55,
        "trailing_activate": 1.35,
        "trailing_distance": 0.35,
    },
    "ETH": {
        "break_even_activate": 0.60,
        "break_even_buffer": 0.18,
        "profit_lock_activate": 1.20,
        "profit_lock_distance": 0.70,
        "trailing_activate": 1.60,
        "trailing_distance": 0.45,
    },
    "LTC": {
        "break_even_activate": 0.75,
        "break_even_buffer": 0.22,
        "profit_lock_activate": 1.45,
        "profit_lock_distance": 0.85,
        "trailing_activate": 1.90,
        "trailing_distance": 0.60,
    },
    "XRP": {
        "break_even_activate": 0.85,
        "break_even_buffer": 0.25,
        "profit_lock_activate": 1.60,
        "profit_lock_distance": 0.95,
        "trailing_activate": 2.10,
        "trailing_distance": 0.70,
    },
}

DEFAULT_PROTECTION_THRESHOLDS = {
    "break_even_activate": 0.65,
    "break_even_buffer": 0.20,
    "profit_lock_activate": 1.30,
    "profit_lock_distance": 0.75,
    "trailing_activate": 1.75,
    "trailing_distance": 0.50,
}


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

    def _exit_price_for_side(self, symbol: str, side: str) -> ExecutionPrice:
        ...

    def _close_position(
        self,
        state: AutoTradeState,
        *,
        forced_reason: str | None = None,
        forced_exit_price: NumericLike | None = None,
        forced_pnl: NumericLike | None = None,
        forced_price_meta: ExecutionPrice | None = None,
    ) -> ExecutionDecision:
        ...

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

    def _runtime_intelligence_close_reason(
        self,
        *,
        state: AutoTradeState,
        current_price: float,
    ) -> str | None:
        ...


class ExecutionPositionProtectionMixin(_ExecutionPositionProtectionProtocol):
    # Главный runtime protection processor.
    # Здесь один раз получаем цену выхода и один раз считаем PositionMetrics.
    def _process_runtime_protection(
        self,
        state: AutoTradeState,
    ) -> ExecutionDecision | None:
        position = type(self)._position

        if position.side == "NONE":
            self._reset_runtime_protection_state(state)
            return None

        try:
            current_execution = self._exit_price_for_side(
                position.symbol or state.symbol,
                position.side,
            )

            current_price = safe_float(current_execution.price)

            if current_price is None or current_price <= 0:
                raise ValueError("invalid execution price")

        except Exception:
            self._sync_runtime_protection_state(
                state=state,
                status="DEGRADED",
                reason="нет актуальной цены для protection engine",
            )
            return None

        metrics = build_position_metrics(
            position,
            current_price=current_price,
        )

        self._sync_runtime_protection_state(
            state=state,
            status="ACTIVE",
            reason="protection engine активен",
        )

        self._update_break_even_protection(
            state=state,
            current_price=current_price,
            metrics=metrics,
        )

        self._update_profit_lock_protection(
            state=state,
            current_price=current_price,
            metrics=metrics,
        )

        self._update_trailing_stop_protection(
            state=state,
            current_price=current_price,
            metrics=metrics,
        )

        close_reason = self._runtime_protection_close_reason(
            state=state,
            current_price=current_price,
        )

        if close_reason is None:
            close_reason = self._runtime_intelligence_close_reason(
                state=state,
                current_price=current_price,
            )

        if close_reason is None:
            return None

        return self._close_position(
            state,
            forced_reason=close_reason,
            forced_exit_price=current_price,
            forced_pnl=metrics.net_pnl_usd,
            forced_price_meta=current_execution,
        )

    def _sync_runtime_protection_state(
        self,
        *,
        state: AutoTradeState,
        status: str,
        reason: str,
    ) -> None:
        state.position_protection_status = status
        state.position_protection_reason = reason
        state.runtime_protection_updated_at = time.monotonic()

    # принудительно усилить защиту позиции по запросу autonomous PROTECT
    def _force_runtime_protect(
        self,
        state: AutoTradeState,
        *,
        reason: str,
    ) -> bool:
        position = type(self)._position

        if position.side == "NONE":
            return False

        try:
            current_execution = self._exit_price_for_side(
                position.symbol or state.symbol,
                position.side,
            )
            current_price = safe_float(current_execution.price)

            if current_price is None or current_price <= 0:
                return False

        except Exception:
            return False

        metrics = build_position_metrics(
            position,
            current_price=current_price,
        )

        entry_price = safe_float(position.entry_price)
        price_move_percent = safe_float(metrics.price_move_percent)

        if entry_price is None or entry_price <= 0:
            return False

        if price_move_percent is None:
            return False

        # PROTECT не должен ставить защиту, если позиция уже в минусе.
        # В минусовой позиции protection-цена может немедленно закрыть сделку
        # или создать ложное ощущение защиты.
        if price_move_percent <= 0:
            return False

        changed = False

        # 1. Если позиция уже в прибыли, но break-even ещё не включён —
        # включаем его сразу, не дожидаясь обычного порога.
        if not state.break_even_armed:
            buffer_percent = 0.03

            if position.side == "LONG":
                state.break_even_price = entry_price * (1 + buffer_percent / 100)
            elif position.side == "SHORT":
                state.break_even_price = entry_price * (1 - buffer_percent / 100)
            else:
                return False

            state.break_even_armed = True
            changed = True

        # 2. Если прибыль уже покрывает хотя бы небольшой запас,
        # подтягиваем profit-lock ближе, чем обычные thresholds.
        # Это помогает не отдавать маленькую прибыль обратно комиссии/шуму.
        if price_move_percent >= 0.25:
            lock_distance_percent = 0.18

            if position.side == "LONG":
                min_lock_price = entry_price * 1.0002
                dynamic_lock_price = current_price * (1 - lock_distance_percent / 100)
                lock_price = max(min_lock_price, dynamic_lock_price)

                previous_price = safe_float(state.profit_lock_price)
                if previous_price is None or lock_price > previous_price:
                    state.profit_lock_active = True
                    state.profit_lock_price = round(lock_price, 8)
                    changed = True

            elif position.side == "SHORT":
                min_lock_price = entry_price * 0.9998
                dynamic_lock_price = current_price * (1 + lock_distance_percent / 100)
                lock_price = min(min_lock_price, dynamic_lock_price)

                previous_price = safe_float(state.profit_lock_price)
                if previous_price is None or lock_price < previous_price:
                    state.profit_lock_active = True
                    state.profit_lock_price = round(lock_price, 8)
                    changed = True

        if not changed:
            return False

        state.runtime_protection_action = "FORCED_PROTECT"
        state.runtime_protection_reason = reason
        state.runtime_protection_updated_at = time.monotonic()

        self._log_runtime_protection_event(
            state=state,
            action="FORCED_PROTECT",
            reason=reason,
            current_price=current_price,
            metrics=metrics,
        )

        return True
    
    def _update_break_even_protection(
        self,
        *,
        state: AutoTradeState,
        current_price: float,
        metrics: PositionMetrics,
    ) -> None:
        position = type(self)._position

        if state.break_even_armed:
            return

        price_move_percent = metrics.price_move_percent
        thresholds = self._protection_thresholds(state)

        if price_move_percent < thresholds["break_even_activate"]:
            return

        entry_price = safe_float(position.entry_price)
        if entry_price is None or entry_price <= 0:
            return

        state.break_even_armed = True

        buffer_percent = thresholds["break_even_buffer"]

        if position.side == "LONG":
            state.break_even_price = entry_price * (1 + buffer_percent / 100)
        elif position.side == "SHORT":
            state.break_even_price = entry_price * (1 - buffer_percent / 100)
        else:
            return

        state.runtime_protection_action = "BREAK_EVEN_ARMED"
        state.runtime_protection_reason = "позиция вышла в прибыль, break-even активирован"
        state.runtime_protection_updated_at = time.monotonic()

        self._log_runtime_protection_event(
            state=state,
            action="BREAK_EVEN_ARMED",
            reason=state.runtime_protection_reason,
            current_price=current_price,
            metrics=metrics,
        )

    def _update_profit_lock_protection(
        self,
        *,
        state: AutoTradeState,
        current_price: float,
        metrics: PositionMetrics,
    ) -> None:
        position = type(self)._position

        price_move_percent = metrics.price_move_percent
        thresholds = self._protection_thresholds(state)

        if price_move_percent < thresholds["profit_lock_activate"]:
            return

        entry_price = safe_float(position.entry_price)
        if entry_price is None or entry_price <= 0:
            return

        lock_distance_percent = thresholds["profit_lock_distance"]

        if position.side == "LONG":
            min_lock_price = entry_price * 1.001
            dynamic_lock_price = current_price * (1 - lock_distance_percent / 100)
            lock_price = max(min_lock_price, dynamic_lock_price)
        elif position.side == "SHORT":
            min_lock_price = entry_price * 0.999
            dynamic_lock_price = current_price * (1 + lock_distance_percent / 100)
            lock_price = min(min_lock_price, dynamic_lock_price)
        else:
            return

        previous_price = safe_float(state.profit_lock_price)

        if previous_price is not None:
            if position.side == "LONG" and lock_price <= previous_price:
                return
            if position.side == "SHORT" and lock_price >= previous_price:
                return

        state.profit_lock_active = True
        state.profit_lock_price = round(lock_price, 8)
        state.runtime_protection_action = "PROFIT_LOCK_ACTIVE"
        state.runtime_protection_reason = "часть прибыли защищена profit lock"
        state.runtime_protection_updated_at = time.monotonic()

        self._log_runtime_protection_event(
            state=state,
            action="PROFIT_LOCK_ACTIVE",
            reason=state.runtime_protection_reason,
            current_price=current_price,
            metrics=metrics,
        )

    def _update_trailing_stop_protection(
        self,
        *,
        state: AutoTradeState,
        current_price: float,
        metrics: PositionMetrics,
    ) -> None:
        position = type(self)._position

        price_move_percent = metrics.price_move_percent
        thresholds = self._protection_thresholds(state)

        if price_move_percent < thresholds["trailing_activate"]:
            return

        trail_distance_percent = thresholds["trailing_distance"]

        if position.side == "LONG":
            trail_price = current_price * (1 - trail_distance_percent / 100)
            previous_price = safe_float(state.trailing_stop_price)

            if previous_price is not None and trail_price <= previous_price:
                return

        elif position.side == "SHORT":
            trail_price = current_price * (1 + trail_distance_percent / 100)
            previous_price = safe_float(state.trailing_stop_price)

            if previous_price is not None and trail_price >= previous_price:
                return

        else:
            return

        state.trailing_stop_active = True
        state.trailing_stop_price = round(trail_price, 8)
        state.runtime_protection_action = "TRAILING_STOP_ACTIVE"
        state.runtime_protection_reason = "trailing stop подтянут вслед за прибылью"
        state.runtime_protection_updated_at = time.monotonic()

        self._log_runtime_protection_event(
            state=state,
            action="TRAILING_STOP_ACTIVE",
            reason=state.runtime_protection_reason,
            current_price=current_price,
            metrics=metrics,
        )

    def _runtime_protection_close_reason(
        self,
        *,
        state: AutoTradeState,
        current_price: float,
    ) -> str | None:
        position = type(self)._position

        if position.side == "LONG":
            if (
                state.trailing_stop_active
                and state.trailing_stop_price is not None
                and current_price <= state.trailing_stop_price
            ):
                return "TRAILING_STOP"

            if (
                state.profit_lock_active
                and state.profit_lock_price is not None
                and current_price <= state.profit_lock_price
            ):
                return "PROFIT_LOCK"

            if (
                state.break_even_armed
                and state.break_even_price is not None
                and current_price <= state.break_even_price
            ):
                return "BREAK_EVEN"

        elif position.side == "SHORT":
            if (
                state.trailing_stop_active
                and state.trailing_stop_price is not None
                and current_price >= state.trailing_stop_price
            ):
                return "TRAILING_STOP"

            if (
                state.profit_lock_active
                and state.profit_lock_price is not None
                and current_price >= state.profit_lock_price
            ):
                return "PROFIT_LOCK"

            if (
                state.break_even_armed
                and state.break_even_price is not None
                and current_price >= state.break_even_price
            ):
                return "BREAK_EVEN"

        return None

    def _build_runtime_protection_payload(
        self,
        *,
        state: AutoTradeState,
        action: str,
        reason: str,
        current_price: float,
        metrics: PositionMetrics,
    ) -> JsonDict:
        position = type(self)._position

        return {
            # ---------- Trade ----------
            "trade_id": position.trade_id,
            "trade_sequence": position.trade_sequence,
            "trade_cycle_number": position.trade_cycle_number,

            # ---------- Event ----------
            "execution_type": "RUNTIME_PROTECTION",
            "action": action,
            "reason": reason,

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

            # ---------- Position ----------
            "symbol": state.symbol,
            "position_side": position.side,
            "entry_price": position.entry_price,
            "current_price": current_price,
            "size": position.size,
            "leverage": position.leverage,

            "opened_at": position.opened_at,
            "updated_at": position.updated_at,

            # ---------- Metrics ----------
            "position_pnl_percent": metrics.price_move_percent,
            "net_pnl_usd": metrics.net_pnl_usd,
            "gross_pnl_usd": metrics.gross_pnl_usd,
            "commission_usd": metrics.commission_usd,
            "overnight_cashflow_usd": metrics.overnight_cashflow_usd,
            "margin_usd": metrics.margin_usd,
            "hold_seconds": metrics.hold_seconds,

            # ---------- Runtime protection ----------
            **build_runtime_protection_payload(state),
            "runtime_protection_updated_at": state.runtime_protection_updated_at,

            # ---------- Protection thresholds ----------
            "protection_thresholds": self._protection_thresholds(state),

            # ---------- Position Intelligence ----------
            **build_position_health_payload(state),
            "position_pressure": state.position_pressure,
            "position_exit_pressure": state.position_exit_pressure,

            **build_full_position_intelligence_payload(state),

            # ---------- Execution ----------
            **build_execution_quality_payload(state),
            **build_execution_price_payload(state),

            # ---------- Market Context ----------
            **build_market_context_payload(state),
        }

    def _log_runtime_protection_event(
        self,
        *,
        state: AutoTradeState,
        action: str,
        reason: str,
        current_price: float,
        metrics: PositionMetrics,
    ) -> None:
        payload = self._build_runtime_protection_payload(
            state=state,
            action=action,
            reason=reason,
            current_price=current_price,
            metrics=metrics,
        )

        JournalService().log_ui_info(
            event_type="runtime_protection_updated",
            message=f"Runtime protection: {action}. {reason}.",
            screen="auto",
            action="runtime_protection",
            payload=payload,
        )

        EventBus.emit("runtime_protection_updated", payload)

    def _asset_symbol(self, symbol: str | None) -> str:
        if not symbol:
            return ""

        base = str(symbol).split("_", 1)[0].upper()

        if "/" in base:
            return base.split("/", 1)[0]

        for suffix in ("USDT", "USD", "EUR", "BTC"):
            if base.endswith(suffix) and len(base) > len(suffix):
                return base[: -len(suffix)]

        return base

    def _protection_thresholds(self, state: AutoTradeState) -> dict[str, float]:
        asset = self._asset_symbol(state.symbol)

        return PROTECTION_THRESHOLDS_BY_ASSET.get(
            asset,
            DEFAULT_PROTECTION_THRESHOLDS,
        )