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

from __future__ import annotations

from dataclasses import dataclass

from src.core.numbers import safe_float
from src.core.types import NumericLike
from src.integrations.exchange.service import ExchangeService
from src.trading.auto.state import AutoTradeState


@dataclass(slots=True)
class ExecutionPrice:
    price: float
    source: str
    age_seconds: float | None
    updated_at: str
    pricing_role: str


class ExecutionPricingMixin:
    # получить цену входа по текущему сигналу
    def _signal_entry_price(self, state: AutoTradeState) -> ExecutionPrice:
        if state.last_signal == "BUY":
            return self._entry_price_for_side(state.symbol, "LONG")

        if state.last_signal == "SELL":
            return self._entry_price_for_side(state.symbol, "SHORT")

        return self._market_last_price(state.symbol)

    # получить цену входа по стороне позиции
    def _entry_price_for_side(self, symbol: str, side: str) -> ExecutionPrice:
        snapshot = ExchangeService().get_execution_snapshot(symbol)
        self._ensure_fresh_snapshot(snapshot.age_seconds)

        if side == "LONG":
            return self._build_execution_price(
                snapshot,
                raw_price=snapshot.ask_price,
                price_name="ask_price",
                pricing_role="LONG_ENTRY_ASK",
            )

        if side == "SHORT":
            return self._build_execution_price(
                snapshot,
                raw_price=snapshot.bid_price,
                price_name="bid_price",
                pricing_role="SHORT_ENTRY_BID",
            )

        return self._build_execution_price(
            snapshot,
            raw_price=snapshot.last_price,
            price_name="last_price",
            pricing_role="ENTRY_LAST",
        )

    # получить цену выхода по стороне позиции
    def _exit_price_for_side(self, symbol: str, side: str) -> ExecutionPrice:
        snapshot = ExchangeService().get_execution_snapshot(symbol)
        self._ensure_fresh_snapshot(snapshot.age_seconds)

        if side == "LONG":
            return self._build_execution_price(
                snapshot,
                raw_price=snapshot.bid_price,
                price_name="bid_price",
                pricing_role="LONG_EXIT_BID",
            )

        if side == "SHORT":
            return self._build_execution_price(
                snapshot,
                raw_price=snapshot.ask_price,
                price_name="ask_price",
                pricing_role="SHORT_EXIT_ASK",
            )

        return self._build_execution_price(
            snapshot,
            raw_price=snapshot.last_price,
            price_name="last_price",
            pricing_role="EXIT_LAST",
        )

    # получить последнюю рыночную цену
    def _market_last_price(self, symbol: str) -> ExecutionPrice:
        snapshot = ExchangeService().get_execution_snapshot(symbol)
        self._ensure_fresh_snapshot(snapshot.age_seconds)

        return self._build_execution_price(
            snapshot,
            raw_price=snapshot.last_price,
            price_name="last_price",
            pricing_role="MARKET_LAST",
        )

    # собрать ExecutionPrice из execution snapshot
    def _build_execution_price(
        self,
        snapshot,
        *,
        raw_price: NumericLike | None,
        price_name: str,
        pricing_role: str,
    ) -> ExecutionPrice:
        return ExecutionPrice(
            price=self._snapshot_price(raw_price, price_name),
            source=snapshot.source,
            age_seconds=snapshot.age_seconds,
            updated_at=snapshot.updated_at,
            pricing_role=pricing_role,
        )

    # проверить и нормализовать цену из execution snapshot
    def _snapshot_price(
        self,
        raw_price: NumericLike | None,
        name: str,
    ) -> float:
        if raw_price is None:
            raise ValueError(f"Execution snapshot price '{name}' is missing.")

        price = safe_float(raw_price)

        if price is None:
            raise ValueError(f"Execution snapshot price '{name}' is invalid.")

        if price <= 0:
            raise ValueError(
                f"Execution snapshot price '{name}' is invalid: {price}"
            )

        return price

    # проверить свежесть execution snapshot
    def _ensure_fresh_snapshot(self, age_seconds: NumericLike | None) -> None:
        age = safe_float(age_seconds)

        if age is None:
            return

        max_age = safe_float(
            getattr(self, "_max_execution_snapshot_age_seconds", None)
        ) or 5.0

        if age > max_age:
            raise ValueError(f"Execution snapshot is stale: {age:.2f}s.")