# app/src/market_data/acquisition/adapters/dzengi/models.py

from __future__ import annotations

from dataclasses import dataclass
from typing import TypeAlias


# Число в исходном JSON-ответе Dzengi без предметного преобразования.
DzengiJsonNumber: TypeAlias = int | float

# Числовое значение, которое Dzengi может передать числом или строкой.
DzengiRawNumeric: TypeAlias = str | int | float

# Скалярное значение неизвестного поля транспортного ответа.
DzengiJsonScalar: TypeAlias = str | int | float | bool | None


# Лимит запросов из exchangeInfo.
@dataclass(frozen=True, slots=True)
class DzengiRateLimit:
    interval: str
    interval_num: int
    limit: int
    rate_limit_type: str


# Базовый контракт фильтра инструмента Dzengi.
@dataclass(frozen=True, slots=True)
class DzengiInstrumentFilter:
    filter_type: str


# Ограничения размера заявки.
@dataclass(frozen=True, slots=True)
class DzengiLotSizeFilter(DzengiInstrumentFilter):
    min_qty: DzengiRawNumeric | None
    max_qty: DzengiRawNumeric | None
    step_size: DzengiRawNumeric | None


# Ограничение минимальной стоимости заявки.
@dataclass(frozen=True, slots=True)
class DzengiMinNotionalFilter(DzengiInstrumentFilter):
    min_notional: DzengiRawNumeric | None


# Неизвестный тип фильтра, который ещё не поддерживается адаптером.
@dataclass(frozen=True, slots=True)
class DzengiUnknownFilter(DzengiInstrumentFilter):
    fields: tuple[tuple[str, DzengiJsonScalar], ...]


# Один инструмент из ответа Dzengi exchangeInfo.
@dataclass(frozen=True, slots=True)
class DzengiExchangeInfoSymbol:
    symbol: str
    name: str
    status: str

    asset_type: str | None

    base_asset: str
    base_asset_precision: int | None

    quote_asset: str
    quote_asset_id: str | None
    quote_precision: int | None

    order_types: tuple[str, ...]
    filters: tuple[DzengiInstrumentFilter, ...]

    market_modes: tuple[str, ...]
    market_type: str

    country: str | None
    sector: str | None
    industry: str | None
    trading_hours: str | None

    tick_size: DzengiJsonNumber | None
    tick_value: DzengiJsonNumber | None

    trading_fee: DzengiJsonNumber | None
    exchange_fee: DzengiJsonNumber | None

    long_rate: DzengiJsonNumber | None
    short_rate: DzengiJsonNumber | None
    swap_charge_interval: int | None

    min_sl_gap: DzengiJsonNumber | None
    max_sl_gap: DzengiJsonNumber | None
    min_tp_gap: DzengiJsonNumber | None
    max_tp_gap: DzengiJsonNumber | None


# Содержимое exchangeInfo независимо от внешней оболочки API.
@dataclass(frozen=True, slots=True)
class DzengiExchangeInfoPayload:
    timezone: str | None
    server_time: int | None
    rate_limits: tuple[DzengiRateLimit, ...]
    exchange_filters: tuple[DzengiUnknownFilter, ...]
    symbols: tuple[DzengiExchangeInfoSymbol, ...]


# Нормализованное транспортное представление ответа exchangeInfo.
@dataclass(frozen=True, slots=True)
class DzengiExchangeInfoResponse:
    payload: DzengiExchangeInfoPayload

    # Поля присутствуют в wrapped-формате ответа и отсутствуют
    # в фактическом unwrapped-ответе публичного REST endpoint.
    status: str | None = None
    correlation_id: str | None = None

# Транспортное представление ответа Dzengi GET /api/v1/ticker/24hr.
@dataclass(frozen=True, slots=True)
class DzengiTicker24hrResponse:
    symbol: str
    last_price: DzengiRawNumeric
    bid_price: DzengiRawNumeric
    ask_price: DzengiRawNumeric
    close_time: int


# Нормализованное транспортное представление котировки из Dzengi WebSocket.
@dataclass(frozen=True, slots=True)
class DzengiWebSocketQuoteResponse:
    symbol: str
    bid_price: DzengiRawNumeric
    ask_price: DzengiRawNumeric
    timestamp: int | None


# Транспортное представление одной свечи Dzengi GET /api/v1/klines.
@dataclass(frozen=True, slots=True)
class DzengiKline:
    open_time: int
    open_price: str | int | float
    high_price: str | int | float
    low_price: str | int | float
    close_price: str | int | float
    volume: str | int | float


# Транспортное представление ответа Dzengi GET /api/v1/klines.
@dataclass(frozen=True, slots=True)
class DzengiKlinesResponse:
    items: tuple[DzengiKline, ...]


# Транспортное представление одного события Dzengi WebSocket ohlc.event.
#
# Событие содержит завершённую OHLC-свечу без объёма и поэтому не является
# канонической моделью Candle. Поле candle_type принимает значения,
# подтверждённые runtime-контрактом Dzengi: classic или heikin-ashi.
@dataclass(frozen=True, slots=True)
class DzengiWebSocketOhlcEvent:
    symbol: str
    interval: str
    candle_type: str

    open_time: int

    open_price: DzengiRawNumeric
    high_price: DzengiRawNumeric
    low_price: DzengiRawNumeric
    close_price: DzengiRawNumeric
