# app/src/trading/strategies/scalp.py

from __future__ import annotations

import time
from typing import Any
from decimal import Decimal

from src.core.types import NumericLike
from src.integrations.exchange.service import ExchangeService
from src.market_data.acquisition.models.quote import Quote
from src.trading.market_analysis.models import (
    MarketState,
    MarketStructure,
    MomentumState,
    TrendDirection,
    VolatilityState,
)
from src.trading.market_analysis.service import MarketAnalysisService
from src.trading.strategies.base import StrategyContext
from src.trading.strategies.signals import SignalResult, SignalType


class ScalpStrategy:
    name = "SCALP"

    _price_window: dict[str, list[float]] = {}
    _window_ttl_seconds = 30
    _price_window_updated_at: dict[str, float] = {}

    _window_size = 4
    _threshold_percent = 0.02
    _min_direction_ratio = 0.55

    # SCALP быстрее TREND, но всё равно использует market-analysis фильтры.
    _market_interval = "1m"

    def reset_runtime(self, symbol: str | None = None) -> None:
        if symbol is None:
            self._price_window.clear()
            self._price_window_updated_at.clear()
            return

        normalized_symbol = symbol.upper()
        keys_to_delete = [
            key for key in self._price_window.keys()
            if key.upper() == normalized_symbol
        ]

        for key in keys_to_delete:
            self._price_window.pop(key, None)
            self._price_window_updated_at.pop(key, None)

    def analyze(self, context: StrategyContext) -> SignalResult:
        market = MarketAnalysisService().analyze(
            context.symbol,
            interval=self._market_interval,
            limit=200,
        )

        try:
            quote = ExchangeService().get_quote(
                context.symbol,
                runtime_key="auto",
            )
        except Exception as exc:
            return SignalResult(
                signal=SignalType.HOLD,
                reason="Не удалось получить рыночный snapshot. Безопасный HOLD.",
                confidence=0.0,
                payload={
                    "strategy": self.name,
                    "symbol": context.symbol,
                    "error": str(exc),
                    "market_analysis": market.payload,
                    "entry_block_reason": "MARKET_SNAPSHOT_ERROR",
                    "entry_block_message": "нет данных рынка",
                },
            )

        symbol = quote.symbol or context.symbol
        current_price = self._analysis_price(quote)

        if current_price <= 0:
            return SignalResult(
                signal=SignalType.HOLD,
                reason="Некорректная рыночная цена. Безопасный HOLD.",
                confidence=0.0,
                payload={
                    "strategy": self.name,
                    "symbol": symbol,
                    "snapshot": self._quote_snapshot(quote),
                    "market_analysis": market.payload,
                    "entry_block_reason": "INVALID_MARKET_PRICE",
                    "entry_block_message": "нет цены",
                },
            )

        now = time.monotonic()
        previous_updated_at = self._price_window_updated_at.get(symbol)

        if (
            previous_updated_at is not None
            and now - previous_updated_at > self._window_ttl_seconds
        ):
            self._price_window.pop(symbol, None)
            self._price_window_updated_at.pop(symbol, None)

        prices = self._price_window.setdefault(symbol, [])
        prices.append(current_price)
        self._price_window_updated_at[symbol] = now

        if len(prices) > self._window_size:
            prices.pop(0)

        base_payload = self._base_payload(
            symbol=symbol,
            current_price=current_price,
            quote=quote,
            market=market,
            prices=prices,
        )

        market_block = self._market_block_signal(
            market=market,
            base_payload=base_payload,
        )

        if market_block is not None:
            return market_block

        breakout_signal = self._breakout_signal(
            market=market,
            base_payload=base_payload,
        )

        if breakout_signal is not None:
            return breakout_signal

        if len(prices) < self._window_size:
            return SignalResult(
                signal=SignalType.HOLD,
                reason="Недостаточно live-данных для SCALP.",
                confidence=0.0,
                payload={
                    **base_payload,
                    "window_size": len(prices),
                    "required_window_size": self._window_size,
                    "entry_block_reason": "NOT_ENOUGH_LIVE_DATA",
                    "entry_block_message": "мало данных",
                },
            )

        first_price = prices[0]
        last_price = prices[-1]

        if first_price <= 0:
            return SignalResult(
                signal=SignalType.HOLD,
                reason="Некорректная стартовая цена в окне SCALP.",
                confidence=0.0,
                payload={
                    **base_payload,
                    "prices": prices,
                    "entry_block_reason": "INVALID_WINDOW_PRICE",
                    "entry_block_message": "ошибка цены",
                },
            )

        change_percent = ((last_price - first_price) / first_price) * 100
        direction_ratio = self._direction_ratio(prices, change_percent)

        payload = {
            **base_payload,
            "first_price": first_price,
            "current_price": last_price,
            "change_percent": round(change_percent, 5),
            "direction_ratio": round(direction_ratio, 3),
            "window_size": len(prices),
            "threshold_percent": self._threshold_percent,
            "min_direction_ratio": self._min_direction_ratio,
        }

        if market.state == MarketState.TREND_UP:
            if (
                change_percent >= self._threshold_percent
                and direction_ratio >= self._min_direction_ratio
            ):
                return SignalResult(
                    signal=SignalType.BUY,
                    reason="SCALP BUY подтверждён трендом и коротким импульсом.",
                    confidence=self._calculate_confidence(change_percent, direction_ratio),
                    payload=payload,
                )

            return SignalResult(
                signal=SignalType.HOLD,
                reason="SCALP: тренд вверх есть, но короткий импульс слабый.",
                confidence=0.0,
                payload={
                    **payload,
                    "entry_block_reason": "WEAK_UP_IMPULSE",
                    "entry_block_message": "слабый импульс",
                    "expected_direction": "BUY",
                },
            )

        if market.state == MarketState.TREND_DOWN:
            if (
                change_percent <= -self._threshold_percent
                and direction_ratio >= self._min_direction_ratio
            ):
                return SignalResult(
                    signal=SignalType.SELL,
                    reason="SCALP SELL подтверждён трендом и коротким импульсом.",
                    confidence=self._calculate_confidence(change_percent, direction_ratio),
                    payload=payload,
                )

            return SignalResult(
                signal=SignalType.HOLD,
                reason="SCALP: тренд вниз есть, но короткий импульс слабый.",
                confidence=0.0,
                payload={
                    **payload,
                    "entry_block_reason": "WEAK_DOWN_IMPULSE",
                    "entry_block_message": "слабый импульс",
                    "expected_direction": "SELL",
                },
            )

        return SignalResult(
            signal=SignalType.HOLD,
            reason=f"Market state не подходит для SCALP: {market.state.value}.",
            confidence=0.0,
            payload={
                **payload,
                "entry_block_reason": "MARKET_STATE_NOT_TREND",
                "entry_block_message": "рынок не трендовый",
            },
        )

    def _market_block_signal(
        self,
        *,
        market: Any,
        base_payload: dict[str, Any],
    ) -> SignalResult | None:
        if market.volatility != VolatilityState.NORMAL:
            return SignalResult(
                signal=SignalType.HOLD,
                reason="SCALP заблокирован: волатильность не NORMAL.",
                confidence=0.0,
                payload={
                    **base_payload,
                    "entry_block_reason": "BAD_SCALP_VOLATILITY",
                    "entry_block_message": "волатильность не подходит",
                },
            )

        if market.htf_alignment == "AGAINST":
            return SignalResult(
                signal=SignalType.HOLD,
                reason="SCALP заблокирован: старший таймфрейм против входа.",
                confidence=0.0,
                payload={
                    **base_payload,
                    "entry_block_reason": "HTF_TREND_AGAINST",
                    "entry_block_message": "старший таймфрейм против входа",
                },
            )

        if market.state not in {MarketState.TREND_UP, MarketState.TREND_DOWN}:
            return SignalResult(
                signal=SignalType.HOLD,
                reason="SCALP заблокирован: нет трендового market state.",
                confidence=0.0,
                payload={
                    **base_payload,
                    "entry_block_reason": "MARKET_STATE_NOT_TREND",
                    "entry_block_message": "рынок не трендовый",
                },
            )

        market_structure = (
            market.market_structure.value
            if market.market_structure is not None
            else "UNKNOWN"
        )

        if market_structure == MarketStructure.MIXED.value:
            return SignalResult(
                signal=SignalType.HOLD,
                reason="SCALP заблокирован: структура рынка смешанная.",
                confidence=0.0,
                payload={
                    **base_payload,
                    "entry_block_reason": "MARKET_STRUCTURE_MIXED",
                    "entry_block_message": "структура не подтверждает вход",
                },
            )

        if market.state == MarketState.TREND_UP and market_structure == MarketStructure.LH_LL.value:
            return SignalResult(
                signal=SignalType.HOLD,
                reason="SCALP заблокирован: структура против LONG.",
                confidence=0.0,
                payload={
                    **base_payload,
                    "entry_block_reason": "MARKET_STRUCTURE_CONFLICT",
                    "entry_block_message": "структура против LONG",
                    "expected_direction": "BUY",
                },
            )

        if market.state == MarketState.TREND_DOWN and market_structure == MarketStructure.HH_HL.value:
            return SignalResult(
                signal=SignalType.HOLD,
                reason="SCALP заблокирован: структура против SHORT.",
                confidence=0.0,
                payload={
                    **base_payload,
                    "entry_block_reason": "MARKET_STRUCTURE_CONFLICT",
                    "entry_block_message": "структура против SHORT",
                    "expected_direction": "SELL",
                },
            )

        return None

    def _breakout_signal(
        self,
        *,
        market: Any,
        base_payload: dict[str, Any],
    ) -> SignalResult | None:
        momentum_state = getattr(market, "momentum_state", MomentumState.UNKNOWN)
        momentum_direction = getattr(market, "momentum_direction", TrendDirection.UNKNOWN)
        momentum_strength = float(getattr(market, "momentum_strength", 0.0) or 0.0)

        if (
            momentum_state == MomentumState.BREAKOUT_UP
            and momentum_direction == TrendDirection.UP
            and market.state == MarketState.TREND_UP
        ):
            return SignalResult(
                signal=SignalType.BUY,
                reason="SCALP BUY по подтверждённому breakout вверх.",
                confidence=self._calculate_breakout_confidence(momentum_strength),
                payload={
                    **base_payload,
                    "breakout_signal": True,
                    "expected_direction": "BUY",
                    "entry_block_reason": None,
                    "entry_block_message": None,
                },
            )

        if (
            momentum_state == MomentumState.BREAKOUT_DOWN
            and momentum_direction == TrendDirection.DOWN
            and market.state == MarketState.TREND_DOWN
        ):
            return SignalResult(
                signal=SignalType.SELL,
                reason="SCALP SELL по подтверждённому breakout вниз.",
                confidence=self._calculate_breakout_confidence(momentum_strength),
                payload={
                    **base_payload,
                    "breakout_signal": True,
                    "expected_direction": "SELL",
                    "entry_block_reason": None,
                    "entry_block_message": None,
                },
            )

        return None

    def _base_payload(
        self,
        *,
        symbol: str,
        current_price: float,
        quote: Quote,
        market: Any,
        prices: list[float],
    ) -> dict[str, Any]:
        return {
            "strategy": self.name,
            "symbol": symbol,
            "analysis_price": current_price,
            "last_price": self._safe_float(quote.last_price),
            "bid_price": self._safe_float(quote.bid_price),
            "ask_price": self._safe_float(quote.ask_price),
            "market_state": market.state.value,
            "market_trend": market.trend.value,
            "market_volatility": market.volatility.value,
            "market_analysis_interval": market.interval,
            "market_analysis_reason": market.reason,
            "market_analysis": market.payload,
            "market_trend_strength": market.trend_strength.value,
            "market_trend_quality": market.trend_quality.value,
            "market_phase": market.market_phase.value,
            "market_phase_direction": market.phase_direction.value,
            "market_phase_change_percent": market.phase_change_percent,
            "market_phase_direction_consistency": market.phase_direction_consistency,
            "market_phase_reason": market.phase_reason,
            "market_structure": (
                market.market_structure.value
                if market.market_structure is not None
                else "UNKNOWN"
            ),
            "market_structure_reason": market.market_structure_reason,
            "momentum_state": (
                market.momentum_state.value
                if market.momentum_state is not None
                else "UNKNOWN"
            ),
            "momentum_direction": (
                market.momentum_direction.value
                if market.momentum_direction is not None
                else "UNKNOWN"
            ),
            "momentum_change_percent": market.momentum_change_percent,
            "momentum_strength": market.momentum_strength,
            "breakout_level": market.breakout_level,
            "breakout_distance_percent": market.breakout_distance_percent,
            "breakout_reason": market.breakout_reason,
            "market_trend_gap_percent": market.trend_gap_percent,
            "market_trend_consistency": market.trend_consistency,
            "market_trend_efficiency": market.trend_efficiency,
            "trend_quality_score": market.trend_quality_score,
            "ema_distance_atr_ratio": market.ema_distance_atr_ratio,
            "ema_distance_state": (
                market.ema_distance_state.value
                if market.ema_distance_state is not None
                else "UNKNOWN"
            ),
            "entry_timing_state": (
                market.entry_timing_state.value
                if market.entry_timing_state is not None
                else "UNKNOWN"
            ),
            "entry_timing_reason": market.entry_timing_reason,
            "candle_noise_score": market.payload.get("candle_noise_score"),
            "price_position_score": market.payload.get("price_position_score"),
            "rsi": market.rsi,
            "rsi_overbought": market.payload.get("rsi_overbought"),
            "rsi_oversold": market.payload.get("rsi_oversold"),
            "htf_interval": market.htf_interval,
            "htf_market_state": (
                market.htf_market_state.value
                if market.htf_market_state is not None
                else "UNKNOWN"
            ),
            "htf_trend": (
                market.htf_trend.value
                if market.htf_trend is not None
                else "UNKNOWN"
            ),
            "htf_trend_strength": (
                market.htf_trend_strength.value
                if market.htf_trend_strength is not None
                else "UNKNOWN"
            ),
            "htf_trend_quality": (
                market.htf_trend_quality.value
                if market.htf_trend_quality is not None
                else "UNKNOWN"
            ),
            "htf_market_phase": (
                market.htf_market_phase.value
                if market.htf_market_phase is not None
                else "UNKNOWN"
            ),
            "htf_alignment": market.htf_alignment,
            "htf_confirmation_score": market.htf_confirmation_score,
            "htf_reason": market.htf_reason,
            "runtime_window_ttl_seconds": self._window_ttl_seconds,
            "runtime_window_size": len(prices),
        }

    def _quote_snapshot(self, quote: Quote) -> dict[str, object]:
        return {
            "symbol": quote.symbol,
            "last_price": self._safe_float(quote.last_price),
            "bid_price": self._safe_float(quote.bid_price),
            "ask_price": self._safe_float(quote.ask_price),
            "source": quote.source,
        }

    def _analysis_price(
        self,
        quote: Quote,
    ) -> float:
        bid = self._safe_float(quote.bid_price)
        ask = self._safe_float(quote.ask_price)

        if bid is not None and ask is not None and bid > 0 and ask > 0:
            return (bid + ask) / 2

        last = self._safe_float(quote.last_price)

        if last is not None and last > 0:
            return last

        return 0.0

    def _safe_float(
        self,
        value: NumericLike | Decimal | None,
    ) -> float | None:
        if value is None:
            return None

        try:
            return float(value)
        except (TypeError, ValueError):
            return None

    def _direction_ratio(self, prices: list[float], change_percent: float) -> float:
        if len(prices) < 2:
            return 0.0

        up_moves = 0
        down_moves = 0

        for previous_price, current_price in zip(prices, prices[1:]):
            if current_price > previous_price:
                up_moves += 1
            elif current_price < previous_price:
                down_moves += 1

        total_moves = max(1, len(prices) - 1)

        if change_percent >= 0:
            return up_moves / total_moves

        return down_moves / total_moves

    def _calculate_breakout_confidence(self, momentum_strength: float) -> float:
        strength_score = min(1.0, max(0.0, momentum_strength) / 2)
        confidence = 0.55 + (strength_score * 0.35)

        return round(min(0.95, confidence), 2)

    def _calculate_confidence(
        self,
        change_percent: float,
        direction_ratio: float,
    ) -> float:
        strength = abs(change_percent) / self._threshold_percent

        if strength < 1:
            return 0.0

        strength_score = min(1.0, strength / 2)
        direction_score = min(1.0, direction_ratio)

        confidence = 0.35 + (strength_score * 0.4) + (direction_score * 0.25)

        return round(min(1.0, confidence), 2)