# app/src/trading/auto/position_semantics.py

from __future__ import annotations

from src.core.numbers import safe_float
from src.trading.auto.state import AutoTradeState
from src.trading.auto.state_reset import reset_position_semantics_state
from src.trading.execution.constants import (
    POSITION_EXIT_DAMPING_MATURE_MULTIPLIER,
    POSITION_EXIT_DAMPING_MATURE_SECONDS,
    POSITION_EXIT_DAMPING_NEW_MULTIPLIER,
    POSITION_EXIT_DAMPING_NEW_SECONDS,
    POSITION_EXIT_SIGNAL_EXIT_CONFIDENCE,
    POSITION_EXIT_SIGNAL_PROTECT_CONFIDENCE,
    POSITION_EXIT_SIGNAL_WATCH_CONFIDENCE,
    POSITION_GIVEBACK_HIGH_PERCENT,
    POSITION_GIVEBACK_LOW_PERCENT,
    POSITION_GIVEBACK_MEDIUM_PERCENT,
    POSITION_LIFECYCLE_ACTIVE_SECONDS,
    POSITION_LIFECYCLE_MATURE_SECONDS,
    POSITION_LIFECYCLE_NEW_SECONDS,
    POSITION_REVERSAL_ELEVATED_GIVEBACK_PERCENT,
    POSITION_REVERSAL_HIGH_GIVEBACK_PERCENT,
    POSITION_STALL_ADVERSE_MAE_PERCENT,
    POSITION_STALL_CONFIRMED_SECONDS,
    POSITION_STALL_DEVELOPING_SECONDS,
    POSITION_STALL_EARLY_SECONDS,
    POSITION_STALL_LOW_PROGRESS_MFE_PERCENT,
    POSITION_STALL_LOW_PROGRESS_PNL_PERCENT,
)


class AutoPositionSemanticsMixin:
    # синхронизировать semantics-состояние открытой позиции
    def _sync_position_semantics_state(self, state: AutoTradeState) -> None:
        if state.position_side == "NONE" or state.entry_price is None:
            reset_position_semantics_state(state)
            return

        lifecycle_stage = self._position_lifecycle_stage(state)
        hold_quality = self._position_hold_quality(state)

        # Передаём свежий lifecycle_stage явно,
        # чтобы decay не читал старое значение из state.
        decay_state = self._position_decay_state(
            state=state,
            lifecycle_stage=lifecycle_stage,
        )

        # Сначала записываем базовые поля.
        # Advanced analytics ниже обновит MFE/MAE/giveback/fatigue,
        # а уже после этого можно корректно рассчитывать stall.
        state.position_lifecycle_stage = lifecycle_stage
        state.position_hold_quality = hold_quality
        state.position_decay_state = decay_state

        self._sync_advanced_position_analytics(
            state=state,
            lifecycle_stage=lifecycle_stage,
            hold_quality=hold_quality,
            decay_state=decay_state,
        )

        # Stall считаем после обновления MFE/MAE,
        # иначе он может читать устаревшее значение position_mfe_percent.
        stall_state, stall_reason = self._position_stall_state(state)
        state.position_stall_state = stall_state
        state.position_stall_reason = stall_reason

        exit_confidence = self._position_exit_confidence(
            state=state,
            hold_quality=hold_quality,
            decay_state=decay_state,
        )

        exit_signal = self._position_exit_signal(exit_confidence)

        state.position_exit_confidence = exit_confidence
        state.position_exit_signal = exit_signal

        # Срочность выхода считаем после записи свежего exit_signal,
        # иначе urgency может читать сигнал прошлого цикла.
        state.position_exit_urgency = self._position_exit_urgency(state)

        state.position_intelligence_reason = self._position_semantics_reason(
            state=state,
            hold_quality=hold_quality,
            decay_state=decay_state,
            exit_signal=exit_signal,
        )
        state.position_recommended_action = self._position_recommended_action(
            exit_signal
        )

    # определить lifecycle stage позиции по времени удержания
    def _position_lifecycle_stage(self, state: AutoTradeState) -> str:
        hold_seconds = state.position_hold_seconds

        if hold_seconds is None:
            return "UNKNOWN"

        if hold_seconds < POSITION_LIFECYCLE_NEW_SECONDS:
            return "NEW"

        if hold_seconds < POSITION_LIFECYCLE_ACTIVE_SECONDS:
            return "ACTIVE"

        if hold_seconds < POSITION_LIFECYCLE_MATURE_SECONDS:
            return "MATURE"

        return "AGED"

    # определить качество удержания позиции
    def _position_hold_quality(self, state: AutoTradeState) -> str:
        health_status = str(state.position_health_status or "").upper()
        pressure = str(state.position_pressure or "").upper()
        trend_alignment = str(state.position_trend_alignment or "").upper()

        if health_status == "DANGER":
            return "BAD"

        if pressure == "HIGH_LOSS":
            return "BAD"

        if trend_alignment == "AGAINST" and state.position_adverse_momentum:
            return "BAD"

        if health_status == "PRESSURE":
            return "WEAK"

        if pressure == "LOSS":
            return "WEAK"

        if pressure in {"PROFIT", "STRONG_PROFIT"} and trend_alignment == "ALIGNED":
            return "GOOD"

        if health_status == "HEALTHY":
            return "GOOD"

        return "NEUTRAL"

    # определить тип ухудшения позиции
    def _position_decay_state(
        self,
        *,
        state: AutoTradeState,
        lifecycle_stage: str,
    ) -> str:
        pressure = str(state.position_pressure or "").upper()
        trend_alignment = str(state.position_trend_alignment or "").upper()
        lifecycle = str(lifecycle_stage or "").upper()

        if pressure in {"HIGH_LOSS", "LOSS"} and state.position_adverse_momentum:
            return "ACCELERATING_LOSS"

        if trend_alignment == "AGAINST" and state.position_adverse_momentum:
            return "CONTEXT_DECAY"

        if pressure == "PROFIT" and state.position_adverse_momentum:
            return "PROFIT_DECAY"

        if lifecycle == "AGED" and pressure == "FLAT":
            return "TIME_DECAY"

        return "NONE"

    # рассчитать confidence для выхода из позиции
    def _position_exit_confidence(
        self,
        *,
        state: AutoTradeState,
        hold_quality: str,
        decay_state: str,
    ) -> float:
        score = 0.0

        risk_level = str(state.position_risk_level or "").upper()
        exit_pressure = str(state.position_exit_pressure or "").upper()
        market_quality = str(
            getattr(state, "market_trend_quality", "") or ""
        ).upper()

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

        adverse_momentum = bool(getattr(state, "position_adverse_momentum", False))
        trend_alignment = str(
            getattr(state, "position_trend_alignment", "") or ""
        ).upper()

        if risk_level == "HIGH":
            score += 0.45
        elif risk_level == "ELEVATED":
            score += 0.30
        elif risk_level == "MODERATE":
            score += 0.15

        if exit_pressure == "HIGH":
            score += 0.30
        elif exit_pressure == "WATCH":
            score += 0.15

        if hold_quality == "BAD":
            score += 0.25
        elif hold_quality == "WEAK":
            score += 0.15

        if decay_state in {"ACCELERATING_LOSS", "CONTEXT_DECAY"}:
            score += 0.25
        elif decay_state in {"PROFIT_DECAY", "TIME_DECAY"}:
            score += 0.15

        if state.execution_quality == "BLOCKED":
            score += 0.10

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

        # Если позиция застряла, повышаем внимание к выходу.
        # Особенно важно для NOISY рынка: там долгое удержание около нуля
        # часто просто накапливает комиссии и даёт серию мелких убытков.
        if stall_state == "ADVERSE_STALLED":
            score += 0.20
        elif stall_state == "NOISY_STALLED":
            score += 0.15
        elif stall_state == "STALLED":
            score += 0.10

        # NOISY рынок не запрещает торговлю полностью,
        # но позицию в шуме нужно сопровождать агрессивнее:
        # если после входа позиция уже в минусе или momentum против неё,
        # повышаем готовность к защите/выходу.
        if market_quality == "NOISY":
            if pnl_percent is not None and pnl_percent < 0:
                score += 0.10

            if adverse_momentum:
                score += 0.12

            if trend_alignment == "AGAINST":
                score += 0.10

        # Новую позицию не закрываем слишком агрессивно:
        # первые минуты часто дают техническую просадку из-за spread/волны.
        #
        # Но если есть реальное ухудшение — HIGH risk, adverse momentum
        # вместе с трендом против позиции — dampening не применяем.
        severe_deterioration = (
            risk_level == "HIGH"
            or (
                adverse_momentum
                and trend_alignment == "AGAINST"
            )
        )

        if not severe_deterioration:
            if hold_seconds < POSITION_EXIT_DAMPING_NEW_SECONDS:
                score *= POSITION_EXIT_DAMPING_NEW_MULTIPLIER
            elif hold_seconds < POSITION_EXIT_DAMPING_MATURE_SECONDS:
                score *= POSITION_EXIT_DAMPING_MATURE_MULTIPLIER

        return round(max(0.0, min(1.0, score)), 3)

    # определить semantic exit signal по confidence
    def _position_exit_signal(self, exit_confidence: float | None) -> str:
        if exit_confidence is None:
            return "NONE"

        if exit_confidence >= POSITION_EXIT_SIGNAL_EXIT_CONFIDENCE:
            return "EXIT"

        if exit_confidence >= POSITION_EXIT_SIGNAL_PROTECT_CONFIDENCE:
            return "REDUCE_OR_PROTECT"

        if exit_confidence >= POSITION_EXIT_SIGNAL_WATCH_CONFIDENCE:
            return "WATCH"

        return "HOLD"

    # сформировать объяснение position semantics
    def _position_semantics_reason(
        self,
        *,
        state: AutoTradeState,
        hold_quality: str,
        decay_state: str,
        exit_signal: str,
    ) -> str:
        if exit_signal == "EXIT":
            return "позиция требует выхода"

        if exit_signal == "REDUCE_OR_PROTECT":
            return "позицию нужно защитить или уменьшить"

        if decay_state != "NONE":
            return "качество удержания ухудшается"

        if hold_quality == "GOOD":
            return "позицию можно удерживать"

        if hold_quality == "WEAK":
            return "позиция требует наблюдения"

        return "критичных признаков выхода нет"

    # определить рекомендуемое действие по exit signal
    def _position_recommended_action(self, exit_signal: str | None) -> str:
        if exit_signal == "EXIT":
            return "CLOSE"

        if exit_signal == "REDUCE_OR_PROTECT":
            return "PROTECT"

        if exit_signal == "WATCH":
            return "WATCH"

        return "HOLD"

    # синхронизировать advanced analytics позиции
    def _sync_advanced_position_analytics(
        self,
        *,
        state: AutoTradeState,
        lifecycle_stage: str,
        hold_quality: str,
        decay_state: str,
    ) -> None:
        pnl = safe_float(state.unrealized_pnl_usd)
        pnl_percent = safe_float(state.position_pnl_percent)

        peak_pnl = safe_float(state.position_peak_pnl_usd)
        peak_pnl_percent = safe_float(state.position_peak_pnl_percent)

        if pnl is not None:
            if peak_pnl is None or pnl > peak_pnl:
                state.position_peak_pnl_usd = pnl

        if pnl_percent is not None:
            if peak_pnl_percent is None or pnl_percent > peak_pnl_percent:
                state.position_peak_pnl_percent = pnl_percent

        state.position_mfe_percent = self._position_mfe_percent(state)
        state.position_mae_percent = self._position_mae_percent(state)
        state.position_giveback_percent = self._position_giveback_percent(state)

        fatigue_score = self._position_fatigue_score(
            state=state,
            lifecycle_stage=lifecycle_stage,
            hold_quality=hold_quality,
            decay_state=decay_state,
        )

        state.position_fatigue_score = fatigue_score
        state.position_fatigue_state = self._position_fatigue_state(fatigue_score)
        state.position_conviction_state = self._position_conviction_state(state)
        state.position_reversal_risk = self._position_reversal_risk(state)

    # рассчитать maximum favorable excursion позиции
    def _position_mfe_percent(self, state: AutoTradeState) -> float | None:
        peak = safe_float(state.position_peak_pnl_percent)

        if peak is None:
            return None

        return round(max(0.0, peak), 4)

    # рассчитать maximum adverse excursion позиции
    def _position_mae_percent(self, state: AutoTradeState) -> float | None:
        current = safe_float(state.position_pnl_percent)

        if current is None:
            return None

        previous_mae = safe_float(state.position_mae_percent)

        # MAE — это максимальное неблагоприятное движение за всю жизнь позиции.
        # Поэтому мы не пересчитываем его от текущего PnL,
        # а сохраняем самый глубокий исторический минус.
        if previous_mae is None:
            return round(min(0.0, current), 4)

        return round(min(previous_mae, current, 0.0), 4)

    # рассчитать процент отдачи прибыли от peak pnl
    def _position_giveback_percent(self, state: AutoTradeState) -> float | None:
        peak = safe_float(state.position_peak_pnl_percent)
        current = safe_float(state.position_pnl_percent)

        if peak is None or current is None:
            return None

        if peak <= 0:
            return 0.0

        giveback = peak - current

        if giveback <= 0:
            return 0.0

        return round((giveback / peak) * 100, 2)

    # рассчитать fatigue score позиции
    def _position_fatigue_score(
        self,
        *,
        state: AutoTradeState,
        lifecycle_stage: str,
        hold_quality: str,
        decay_state: str,
    ) -> float:
        score = 0.0

        giveback = safe_float(state.position_giveback_percent) or 0.0
        hold_seconds = safe_float(state.position_hold_seconds) or 0.0

        if lifecycle_stage == "AGED":
            score += 0.25
        elif lifecycle_stage == "MATURE":
            score += 0.15

        if hold_quality == "BAD":
            score += 0.30
        elif hold_quality == "WEAK":
            score += 0.18

        if decay_state in {"ACCELERATING_LOSS", "CONTEXT_DECAY"}:
            score += 0.30
        elif decay_state in {"PROFIT_DECAY", "TIME_DECAY"}:
            score += 0.18

        if giveback >= POSITION_GIVEBACK_HIGH_PERCENT:
            score += 0.30
        elif giveback >= POSITION_GIVEBACK_MEDIUM_PERCENT:
            score += 0.20
        elif giveback >= POSITION_GIVEBACK_LOW_PERCENT:
            score += 0.10

        if hold_seconds >= POSITION_LIFECYCLE_MATURE_SECONDS * 2:
            score += 0.15
        elif hold_seconds >= POSITION_LIFECYCLE_MATURE_SECONDS:
            score += 0.08

        if state.position_adverse_momentum:
            score += 0.15

        return round(max(0.0, min(1.0, score)), 3)

    # определить fatigue state позиции
    def _position_fatigue_state(self, score: float | None) -> str:
        value = safe_float(score)

        if value is None:
            return "UNKNOWN"

        if value >= 0.75:
            return "EXHAUSTED"

        if value >= 0.50:
            return "TIRED"

        if value >= 0.25:
            return "WATCH"

        return "FRESH"

    # определить conviction state позиции
    def _position_conviction_state(self, state: AutoTradeState) -> str:
        health = str(state.position_health_status or "").upper()
        fatigue = str(state.position_fatigue_state or "").upper()
        alignment = str(state.position_trend_alignment or "").upper()

        if health == "DANGER":
            return "BROKEN"

        if fatigue == "EXHAUSTED" and alignment == "AGAINST":
            return "BROKEN"

        if alignment == "AGAINST" or fatigue == "TIRED":
            return "WEAKENING"

        if health == "HEALTHY" and alignment == "ALIGNED":
            return "STRONG"

        return "NEUTRAL"

    # определить срочность выхода из позиции
    def _position_exit_urgency(self, state: AutoTradeState) -> str:
        exit_signal = str(state.position_exit_signal or "").upper()
        fatigue = str(state.position_fatigue_state or "").upper()
        risk = str(state.position_risk_level or "").upper()

        if exit_signal == "EXIT" and risk == "HIGH":
            return "IMMEDIATE"

        if fatigue == "EXHAUSTED":
            return "HIGH"

        if exit_signal == "REDUCE_OR_PROTECT" or fatigue == "TIRED":
            return "MEDIUM"

        if exit_signal == "WATCH":
            return "LOW"

        return "NONE"

    # определить риск разворота позиции
    def _position_reversal_risk(self, state: AutoTradeState) -> str:
        giveback = safe_float(state.position_giveback_percent) or 0.0
        fatigue = str(state.position_fatigue_state or "").upper()
        adverse = bool(state.position_adverse_momentum)

        if adverse and giveback >= POSITION_REVERSAL_HIGH_GIVEBACK_PERCENT:
            return "HIGH"

        if fatigue in {"TIRED", "EXHAUSTED"} and giveback >= POSITION_REVERSAL_ELEVATED_GIVEBACK_PERCENT:
            return "ELEVATED"

        if adverse:
            return "MODERATE"

        return "LOW"

    # определить, застряла ли позиция без нормального движения
    def _position_stall_state(self, state: AutoTradeState) -> tuple[str, str]:
        hold_seconds = safe_float(getattr(state, "position_hold_seconds", None)) or 0.0
        pnl_percent = safe_float(getattr(state, "position_pnl_percent", None))
        mfe = max(
            safe_float(state.position_peak_pnl_percent) or 0.0,
            safe_float(state.position_pnl_percent) or 0.0,
        )
        mae = safe_float(getattr(state, "position_mae_percent", None)) or 0.0

        market_quality = str(
            getattr(state, "market_trend_quality", "") or ""
        ).upper()

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

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

        if hold_seconds < POSITION_STALL_EARLY_SECONDS:
            return "EARLY", "позиция открыта недавно"

        if pnl_percent is None:
            return "NONE", "нет данных PnL"

        # low_progress = позиция не дала нормального плюса
        # и сейчас находится около нуля.
        # MAE используем отдельно: если был глубокий минус,
        # это уже не просто "стоит", а ухудшение качества позиции.
        low_progress = (
            abs(pnl_percent) <= POSITION_STALL_LOW_PROGRESS_PNL_PERCENT
            and mfe <= POSITION_STALL_LOW_PROGRESS_MFE_PERCENT
        )
        had_adverse_excursion = mae <= POSITION_STALL_ADVERSE_MAE_PERCENT

        if not low_progress:
            return "NONE", "позиция развивается"

        # Пока прошло меньше 10 минут —
        # обычный рынок ещё может "раскачаться".
        if hold_seconds < POSITION_STALL_DEVELOPING_SECONDS:
            return "NONE", "позиция ещё развивается"

        # После 10 минут рынок уже начинает говорить сам за себя.
        if adverse_momentum or trend_alignment == "AGAINST" or had_adverse_excursion:
            return "ADVERSE_STALLED", "позиция застряла, рынок против неё"

        if market_quality == "NOISY":
            return "NOISY_STALLED", "позиция застряла в шумном рынке"

        # Только для обычного рынка спустя длительное время.
        if hold_seconds >= POSITION_STALL_CONFIRMED_SECONDS:
            return "STALLED", "позиция долго не развивается"

        return "NONE", "критичного застоя нет"