# app/src/integrations/exchange/status.py

from __future__ import annotations

from dataclasses import dataclass
from enum import StrEnum

from src.integrations.exchange.exceptions import (
    ExchangeConnectionError,
    ExchangeResponseError,
)
from src.market_data.acquisition.models.status import (
    InstrumentTradingState,
    classify_instrument_status,
)


class ExchangeStatusCode(StrEnum):
    OPEN = "OPEN"
    BREAK = "BREAK"
    EXCHANGE_UNAVAILABLE = "EXCHANGE_UNAVAILABLE"
    AUTH_ERROR = "AUTH_ERROR"
    TIME_ERROR = "TIME_ERROR"
    INVALID_SYMBOL = "INVALID_SYMBOL"
    UNKNOWN = "UNKNOWN"


@dataclass(slots=True)
class ExchangeRuntimeStatus:
    code: ExchangeStatusCode
    is_open: bool
    is_available: bool
    is_auth_ok: bool
    title: str
    message: str
    ui_line: str
    reason: str
    symbol: str | None = None
    raw_status: str | None = None
    raw_error: str | None = None

    # Вернуть статус в dict для старого UI-кода на время миграции.
    def as_dict(self) -> dict[str, object]:
        return {
            "code": self.code.value,
            "status": self.code.value,
            "symbol": self.symbol,
            "is_open": self.is_open,
            "is_available": self.is_available,
            "is_auth_ok": self.is_auth_ok,
            "title": self.title,
            "message": self.message,
            "ui_line": self.ui_line,
            "reason": self.reason,
            "raw_status": self.raw_status,
            "raw_error": self.raw_error,
        }


def build_market_stale_status(
    *,
    symbol: str,
    age_seconds: float | None,
    updated_at: str | None = None,
) -> ExchangeRuntimeStatus:
    age_text = "неизвестно" if age_seconds is None else f"{age_seconds:.0f}с"
    updated_text = f" Последнее обновление: {updated_at}." if updated_at else ""

    return ExchangeRuntimeStatus(
        code=ExchangeStatusCode.BREAK,
        is_open=False,
        is_available=True,
        is_auth_ok=True,
        title="Рынок закрыт",
        message=(
            f"Котировки по инструменту не обновляются. "
            f"Возраст данных: {age_text}.{updated_text}"
        ),
        ui_line="⏸️ Рынок закрыт",
        reason="market_data_stale",
        raw_status="STALE_MARKET_DATA",
        symbol=symbol,
    )


# Собрать статус mock-режима.
def build_mock_exchange_status(*, symbol: str) -> ExchangeRuntimeStatus:
    return ExchangeRuntimeStatus(
        code=ExchangeStatusCode.OPEN,
        is_open=True,
        is_available=True,
        is_auth_ok=True,
        title="Mock exchange",
        message="Mock market is open.",
        ui_line="🟢 Mock биржа",
        reason="mock_exchange",
        symbol=symbol,
        raw_status="OPEN",
    )


# Собрать статус ошибки авторизации аккаунта.
def build_account_auth_status(exc: Exception) -> ExchangeRuntimeStatus:
    return build_exchange_error_status(exc)


# Собрать legacy runtime-статус по канонической классификации инструмента.
def build_market_status_from_symbol_status(
    *,
    raw_status: str | None,
    symbol: str,
) -> ExchangeRuntimeStatus:
    classification = classify_instrument_status(raw_status)
    normalized_status = classification.normalized_status

    if classification.state == InstrumentTradingState.OPEN:
        return ExchangeRuntimeStatus(
            code=ExchangeStatusCode.OPEN,
            is_open=True,
            is_available=True,
            is_auth_ok=True,
            title="Биржа доступна",
            message="Рынок открыт.",
            ui_line="🟢 Биржа доступна",
            reason="market_open",
            raw_status=normalized_status,
            symbol=symbol,
        )

    if classification.state == InstrumentTradingState.NOT_TRADABLE:
        return ExchangeRuntimeStatus(
            code=ExchangeStatusCode.BREAK,
            is_open=False,
            is_available=True,
            is_auth_ok=True,
            title="Рынок недоступен",
            message=f"Этот рынок недоступен для торговли: {symbol}.",
            ui_line="⛔️ Рынок недоступен для торговли",
            reason="market_not_tradable",
            raw_status=normalized_status,
            symbol=symbol,
        )

    if classification.state == InstrumentTradingState.BREAK:
        return ExchangeRuntimeStatus(
            code=ExchangeStatusCode.BREAK,
            is_open=False,
            is_available=True,
            is_auth_ok=True,
            title="Перерыв в торгах",
            message=f"Торги по {symbol} временно остановлены.",
            ui_line="⏸️ Перерыв в торгах",
            reason="market_break",
            raw_status=normalized_status,
            symbol=symbol,
        )

    return ExchangeRuntimeStatus(
        code=ExchangeStatusCode.UNKNOWN,
        is_open=False,
        is_available=True,
        is_auth_ok=True,
        title="Статус торгов неизвестен",
        message=(
            f"Биржа вернула неизвестный статус инструмента"
            f"{f': {normalized_status}' if normalized_status else ''}."
        ),
        ui_line="⚠️ Статус торгов неизвестен",
        reason="market_status_unknown",
        raw_status=normalized_status,
        symbol=symbol,
    )


# Собрать единый статус для неверного торгового инструмента.
def build_invalid_symbol_status(
    *,
    symbol: str,
    message: str,
) -> ExchangeRuntimeStatus:
    return ExchangeRuntimeStatus(
        code=ExchangeStatusCode.INVALID_SYMBOL,
        is_open=False,
        is_available=True,
        is_auth_ok=True,
        title="Инструмент недоступен",
        message=message or f"Инструмент {symbol} недоступен.",
        ui_line="⛔️ Инструмент недоступен",
        reason="invalid_symbol",
        raw_status="INVALID_SYMBOL",
        symbol=symbol,
    )


# Собрать единый статус по ошибке exchange/API.
def build_exchange_error_status(exc: Exception) -> ExchangeRuntimeStatus:
    error_type = classify_exchange_error(exc)
    raw_error = str(exc)

    if error_type == "auth":
        return ExchangeRuntimeStatus(
            code=ExchangeStatusCode.AUTH_ERROR,
            is_open=False,
            is_available=True,
            is_auth_ok=False,
            title="Ошибка доступа к аккаунту",
            message="Ошибка доступа к аккаунту.",
            ui_line="⛔️ Ошибка доступа к аккаунту",
            reason="auth_error",
            raw_status="AUTH_ERROR",
            raw_error=raw_error,
        )

    if error_type == "time":
        return ExchangeRuntimeStatus(
            code=ExchangeStatusCode.TIME_ERROR,
            is_open=False,
            is_available=False,
            is_auth_ok=True,
            title="Ошибка времени",
            message="Проверь синхронизацию времени.",
            ui_line="⛔️ Ошибка времени биржи",
            reason="time_error",
            raw_status="TIME_ERROR",
            raw_error=raw_error,
        )

    return ExchangeRuntimeStatus(
        code=ExchangeStatusCode.EXCHANGE_UNAVAILABLE,
        is_open=False,
        is_available=False,
        is_auth_ok=True,
        title="Биржа недоступна",
        message="Не удалось получить данные с биржи.",
        ui_line="⛔️ Биржа недоступна",
        reason="exchange_unavailable",
        raw_status="EXCHANGE_UNAVAILABLE",
        raw_error=raw_error,
    )


# Классифицировать ошибку биржи для единого UI и логов.
def classify_exchange_error(exc: Exception) -> str:
    text = str(exc).lower()

    if any(
        marker in text
        for marker in [
            "invalid api key",
            "invalid api-key",
            "api key",
            "api-key",
            "signature",
            "unauthorized",
            "forbidden",
            "permissions",
            "expired",
        ]
    ):
        return "auth"

    if any(
        marker in text
        for marker in [
            "-1021",
            "server time",
            "doesn't match server time",
            "рассинхрон",
        ]
    ):
        return "time"

    if isinstance(exc, ExchangeConnectionError):
        return "network"

    if isinstance(exc, ExchangeResponseError):
        if "404" in text:
            return "network"

    if any(
        marker in text
        for marker in [
            "404",
            "timeout",
            "timed out",
            "connection error",
            "network error",
            "name or service not known",
            "nodename nor servname",
            "temporary failure",
        ]
    ):
        return "network"

    return "generic"


# Проверить, относится ли reason к unified exchange status layer.
def is_exchange_status_reason(reason: str | None) -> bool:
    if not reason:
        return False

    normalized = str(reason).strip().upper()

    return normalized in {
        ExchangeStatusCode.OPEN.value,
        ExchangeStatusCode.BREAK.value,
        ExchangeStatusCode.EXCHANGE_UNAVAILABLE.value,
        ExchangeStatusCode.AUTH_ERROR.value,
        ExchangeStatusCode.TIME_ERROR.value,
        ExchangeStatusCode.INVALID_SYMBOL.value,
        ExchangeStatusCode.UNKNOWN.value,
    }