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

from __future__ import annotations

import asyncio
import time
from datetime import datetime

from src.core.config import load_settings
from src.core.event_bus import EventBus
from src.core.numbers import safe_float
from src.core.types import NumericLike
from src.trading.auto.state import AutoTradeState
from src.trading.auto.state_reset import (
    reset_adaptive_size_state,
    reset_autonomous_runtime_state,
    reset_cycle_statistics_state,
    reset_execution_runtime_state,
    reset_flip_runtime_state,
    reset_loss_cooldown_state,
    reset_market_analysis_state,
    reset_position_protection_state,
    reset_position_semantics_state,
    reset_runtime_expiration_state,
    reset_signal_runtime_state,
    reset_execution_block_state,
    reset_position_health_state,
)
from src.trading.execution.engine import ExecutionEngine
from src.trading.strategies.base import BaseStrategy, StrategyContext
from src.trading.strategies.registry import StrategyRegistry
from src.trading.auto.execution_quality import AutoExecutionQualityMixin
from src.trading.auto.signal_runtime import AutoSignalRuntimeMixin
from src.trading.auto.market_runtime import AutoMarketRuntimeMixin
from src.trading.auto.position_semantics import AutoPositionSemanticsMixin
from src.trading.auto.position_health import AutoPositionHealthMixin
from src.trading.auto.execution_semantic import AutoExecutionSemanticMixin
from src.trading.auto.autonomous_management import AutoAutonomousManagementMixin
from src.trading.journal.service import JournalService


class AutoLifecycleMixin(
    AutoSignalRuntimeMixin,
    AutoExecutionQualityMixin,
    AutoMarketRuntimeMixin,
    AutoPositionHealthMixin,
    AutoPositionSemanticsMixin,
    AutoAutonomousManagementMixin,
    AutoExecutionSemanticMixin,
):
    _state: AutoTradeState
    _loop_task: asyncio.Task | None
    _loop_interval_seconds: int

    _confirm_min_duration_seconds: int
    _confirm_repeats: int
    _execution_confidence_required_score: float

    # собрать payload изменения статуса автоторговли без изменения состояния
    def _build_auto_status_changed_payload(
        self,
        *,
        previous_status: str,
        new_status: str,
        state: AutoTradeState,
    ) -> dict[str, object]:
        return {
            "previous_status": previous_status,
            "new_status": new_status,
            "symbol": state.symbol,
            "strategy": state.strategy,
            "cycle_number": state.cycle_number,
            "risk_percent": state.risk_percent,
            "leverage": state.leverage,
            "allocated_balance_usd": state.allocated_balance_usd,
            "stop_loss_percent": state.stop_loss_percent,
            "take_profit_percent": state.take_profit_percent,
            "max_loss_usd": state.max_loss_usd,
            "max_reserved_balance_percent": state.max_reserved_balance_percent,
        }
    
    def _log_auto_status_changed(
        self,
        *,
        previous_status: str,
        new_status: str,
        action: str,
        message: str,
    ) -> None:
        state = self.get_state()

        JournalService().log_ui_info(
            event_type="auto_status_changed",
            message=message,
            screen="auto",
            action=action,
            payload=self._build_auto_status_changed_payload(
                previous_status=previous_status,
                new_status=new_status,
                state=state,
            ),
        )

    def set_allocated_balance_usd(self, value: NumericLike) -> AutoTradeState:
        state = self.get_state()

        numeric_value = safe_float(value)

        if numeric_value is None or numeric_value <= 0:
            numeric_value = 1000.0

        state.allocated_balance_usd = numeric_value
        reset_execution_block_state(state)
        state.execution_size_adjustment_reason = None
        return state

    def get_state(self) -> AutoTradeState:
        if not self._state.symbol:
            self._state.symbol = load_settings().default_symbol
        return self._state

    def is_loop_running(self) -> bool:
        return self._loop_task is not None and not self._loop_task.done()

    def start_loop(self) -> None:
        if self.is_loop_running():
            return

        self._loop_task = asyncio.create_task(self._loop_worker())

    def stop_loop(self) -> None:
        if self._loop_task is None:
            return

        self._loop_task.cancel()
        self._loop_task = None

    async def _loop_worker(self) -> None:
        while True:
            state = self.get_state()

            if state.status == "OFF":
                break

            self.run_cycle()
            await asyncio.sleep(self._loop_interval_seconds)

    def start(self) -> tuple[AutoTradeState, str]:
        state = self.get_state()
        previous_status = state.status

        if state.status == "RUNNING":
            return state, "Автоторговля уже активна."

        if state.status == "OBSERVING":
            state.status = "RUNNING"
            if state.cycle_started_at is None:
                state.cycle_started_at = time.monotonic()
                state.cycle_number = int(getattr(state, "cycle_number", 0) or 0) + 1
            # При ручном запуске из OBSERVING очищаем старую cooldown-блокировку,
            # чтобы запуск не наследовал паузу прошлого цикла.
            reset_loss_cooldown_state(state)

            reset_execution_block_state(state)

            EventBus.emit(
                "auto_status_changed",
                {
                    "previous_status": previous_status,
                    "status": state.status,
                },
            )

            self._log_auto_status_changed(
                previous_status=previous_status,
                new_status=state.status,
                action="start",
                message="Автоторговля активирована.",
            )

            return state, "Автоторговля активирована."

        state.status = "RUNNING"
        self._reset_signal_tracking()
        reset_cycle_statistics_state(state)
        reset_loss_cooldown_state(state)
        reset_flip_runtime_state(state)

        state.cycle_started_at = time.monotonic()

        state.last_signal = "HOLD"
        state.signal_started_at = time.monotonic()

        state.cycle_number = int(getattr(state, "cycle_number", 0) or 0) + 1

        EventBus.emit(
            "auto_status_changed",
            {
                "previous_status": previous_status,
                "status": state.status,
            },
        )

        self._log_auto_status_changed(
            previous_status=previous_status,
            new_status=state.status,
            action="start",
            message="Автоторговля запущена.",
        )

        return state, "Автоторговля запущена."

    def observe(self) -> tuple[AutoTradeState, str]:
        state = self.get_state()
        previous_status = state.status

        if previous_status == "OBSERVING":
            return state, "Режим наблюдения уже включён."

        state.status = "OBSERVING"

        EventBus.emit(
            "auto_status_changed",
            {
                "previous_status": previous_status,
                "status": state.status,
            },
        )

        if previous_status == "OFF":
            reset_cycle_statistics_state(state)
            reset_loss_cooldown_state(state)
            reset_flip_runtime_state(state)
            reset_execution_runtime_state(state)
            reset_position_semantics_state(state)

            state.cycle_started_at = None

            self._log_auto_status_changed(
                previous_status=previous_status,
                new_status=state.status,
                action="observe",
                message="Включён режим наблюдения.",
            )

            return state, "Включён режим наблюдения."

        self._log_auto_status_changed(
            previous_status=previous_status,
            new_status=state.status,
            action="observe",
            message="Автоторговля переведена в режим наблюдения.",
        )

        return state, "Автоторговля переведена в режим наблюдения."

    def stop(self) -> tuple[AutoTradeState, str]:
        state = self.get_state()
        previous_status = state.status

        if state.status == "OFF":
            self.stop_loop()
            return state, "Автоторговля уже выключена."

        state.status = "OFF"
        reset_cycle_statistics_state(state)
        reset_loss_cooldown_state(state)
        reset_execution_runtime_state(state)
        reset_adaptive_size_state(state)
        reset_flip_runtime_state(state)
        reset_position_semantics_state(state)

        state.cycle_started_at = None
        self.stop_loop()

        EventBus.emit(
            "auto_status_changed",
            {
                "previous_status": previous_status,
                "status": state.status,
            },
        )

        self._log_auto_status_changed(
            previous_status=previous_status,
            new_status=state.status,
            action="stop",
            message="Автоторговля выключена.",
        )

        return state, "Автоторговля выключена."

    def set_symbol(self, symbol: str) -> AutoTradeState:
        state = self.get_state()
        previous_symbol = state.symbol

        state.symbol = symbol
        self._reset_signal_tracking()

        StrategyRegistry.reset_runtime(symbol=previous_symbol)
        StrategyRegistry.reset_runtime(symbol=symbol)

        return state

    def set_strategy(self, strategy: str) -> AutoTradeState:
        state = self.get_state()
        previous_strategy = state.strategy
        normalized_strategy = strategy.strip().upper()

        state.strategy = normalized_strategy
        self._reset_signal_tracking()

        StrategyRegistry.reset_runtime(previous_strategy)
        StrategyRegistry.reset_runtime(normalized_strategy)

        return state

    def set_risk_percent(self, risk_percent: NumericLike) -> AutoTradeState:
        state = self.get_state()
        state.risk_percent = safe_float(risk_percent)
        return state

    def set_leverage(self, leverage: NumericLike) -> AutoTradeState:
        state = self.get_state()
        state.leverage = safe_float(leverage)
        return state

    def set_stop_loss_percent(self, value: NumericLike | None) -> AutoTradeState:
        state = self.get_state()
        state.stop_loss_percent = safe_float(value)
        return state

    def set_take_profit_percent(self, value: NumericLike | None) -> AutoTradeState:
        state = self.get_state()
        state.take_profit_percent = safe_float(value)
        return state

    def set_max_loss_usd(self, value: NumericLike | None) -> AutoTradeState:
        state = self.get_state()
        state.max_loss_usd = safe_float(value)
        return state

    def set_max_reserved_balance_percent(self, value: NumericLike | None) -> AutoTradeState:
        state = self.get_state()
        state.max_reserved_balance_percent = safe_float(value)
        reset_execution_block_state(state)
        return state

    def _reset_signal_tracking(self) -> None:
        self._last_signal_key = None
        self._last_signal_value = None
        self._last_signal_reason = ""
        self._last_signal_confidence = 0.0
        self._last_signal_payload = None
        self._last_signal_started_at = None
        self._same_signal_count = 0

        state = self.get_state()

        reset_adaptive_size_state(state)
        reset_signal_runtime_state(state)
        reset_execution_runtime_state(state)
        reset_market_analysis_state(state)
        reset_runtime_expiration_state(state)
        reset_position_semantics_state(state)
        reset_position_protection_state(state)
        reset_autonomous_runtime_state(state)
        reset_loss_cooldown_state(state)

        state.signal_confirmation_required_seconds = self._confirm_min_duration_seconds
        state.signal_confirmation_missing_repeats = self._confirm_repeats
        state.execution_confidence_required_score = (
            self._execution_confidence_required_score
        )

        reset_position_health_state(state)

    def _build_strategy_context(self) -> StrategyContext:
        state = self.get_state()

        return StrategyContext(
            symbol=state.symbol,
            status=state.status,
            risk_percent=state.risk_percent,
        )

    def _get_strategy(self) -> BaseStrategy:
        state = self.get_state()
        return StrategyRegistry.get(state.strategy)

    def run_cycle(self) -> AutoTradeState:
        state = self.get_state()

        if state.status == "OFF":
            return state

        if not self._sync_market_availability_state(state):
            state.last_check_at = datetime.now().strftime("%H:%M:%S")
            self._sync_execution_semantic_state(state)
            return state

        self._expire_runtime_if_needed(state)

        strategy = self._get_strategy()
        context = self._build_strategy_context()
        # Последовательность принятия решения:
        # 1. Проверяем доступность рынка и live-данных.
        # 2. Стратегия анализирует свечи 5m + HTF 1h + live snapshot.
        # 3. Стратегия возвращает HOLD / BUY / SELL.
        # 4. Market runtime переносит payload стратегии в общий state.
        # 5. Execution quality проверяет spread, свежесть цены и стакан.
        # 6. Signal runtime подтверждает BUY/SELL по повторам и времени.
        # 7. ExecutionEngine открывает сделку только если сигнал READY.
        # 8. Если позиция открыта — protection/semantics решают,
        #    удерживать, защищать или закрывать позицию.
        result = strategy.analyze(context)

        self._sync_market_analysis_state(
            state=state,
            payload=result.payload,
        )

        self._sync_execution_quality_state(state)

        engine = ExecutionEngine()

        # Перед health/semantics обновляем runtime PnL позиции,
        # иначе position intelligence может работать по данным прошлого цикла.
        engine._update_unrealized_pnl(state)

        # ВАЖНО:
        # раньше position health/intelligence обновлялись только после ExecutionEngine.process().
        # Из-за этого runtime protection внутри execution мог принимать решение
        # по старому состоянию позиции.
        #
        # Теперь перед execution обновляем:
        # - health позиции
        # - semantics позиции
        # - autonomous management
        #
        # Это уменьшает задержку реакции защиты на смену trend/momentum/market context.
        self._sync_position_health_state(state)
        self._sync_position_semantics_state(state)
        self._sync_autonomous_trade_management(state)

        state.last_check_at = datetime.now().strftime("%H:%M:%S")

        self._log_signal_if_changed(
            strategy_name=strategy.name,
            state=state,
            signal=result.signal.value,
            reason=result.reason,
            confidence=result.confidence,
            payload=result.payload,
        )

        engine.process(state)

        # Повторная синхронизация после execution:
        # если позиция была открыта/закрыта/перевернута, UI и runtime state
        # сразу получают актуальное состояние.
        self._sync_position_health_state(state)
        self._sync_position_semantics_state(state)
        self._sync_autonomous_trade_management(state)

        if state.execution_quality != "BLOCKED" and engine.get_position().side != "NONE":
            engine.process_runtime_action(state)

        self._sync_execution_semantic_state(state)

        return state