from __future__ import annotations

from dataclasses import asdict, dataclass, field
from datetime import datetime, timezone
from typing import Any


def utc_now() -> datetime:
    return datetime.now(timezone.utc)


@dataclass(slots=True)
class Candle:
    timestamp: int
    open: float
    high: float
    low: float
    close: float
    volume: float
    turnover: float = 0.0
    ema_20: float | None = None
    ema_50: float | None = None
    ema_200: float | None = None
    rsi_14: float | None = None
    atr_14: float | None = None
    macd: float | None = None
    macd_signal: float | None = None
    macd_hist: float | None = None
    volume_ma_20: float | None = None

    def as_dict(self) -> dict[str, Any]:
        return asdict(self)


@dataclass(slots=True)
class Ticker:
    symbol: str
    last_price: float
    bid: float
    ask: float
    turnover_24h: float
    volume_24h: float
    change_24h: float
    updated_at: datetime = field(default_factory=utc_now)

    @property
    def spread_percent(self) -> float:
        if self.bid <= 0 or self.ask <= 0:
            return 0.0
        mid = (self.ask + self.bid) / 2
        return ((self.ask - self.bid) / mid) * 100 if mid else 0.0

    def as_dict(self) -> dict[str, Any]:
        data = asdict(self)
        data["updated_at"] = self.updated_at.isoformat()
        data["spread_percent"] = self.spread_percent
        return data


@dataclass(slots=True)
class Signal:
    symbol: str
    action: str
    confidence: float
    reason: str
    diagnostics: dict[str, Any] = field(default_factory=dict)
    created_at: datetime = field(default_factory=utc_now)

    def as_dict(self) -> dict[str, Any]:
        data = asdict(self)
        data["created_at"] = self.created_at.isoformat()
        return data


@dataclass(slots=True)
class Position:
    id: int | None
    symbol: str
    qty: float
    entry_price: float
    notional_usdt: float
    entry_fee_usdt: float
    stop_loss: float
    take_profit: float
    highest_price: float
    opened_at: datetime = field(default_factory=utc_now)
    entry_reason: str = ""
    entry_confidence: float = 0.0
    entry_pattern: str = ""
    entry_diagnostics: dict[str, Any] = field(default_factory=dict)

    def mark_price(self, price: float) -> float:
        return self.qty * price

    def unrealized_pnl(self, price: float) -> float:
        return (price - self.entry_price) * self.qty - self.entry_fee_usdt

    def trailing_stop(self, percent: float) -> float | None:
        stop = self.highest_price * (1 - percent)
        return stop if stop > self.entry_price else None

    def as_dict(self, mark_price: float | None = None) -> dict[str, Any]:
        data = asdict(self)
        data["opened_at"] = self.opened_at.isoformat()
        if mark_price is not None:
            data["mark_price"] = mark_price
            data["market_value"] = self.mark_price(mark_price)
            data["unrealized_pnl"] = self.unrealized_pnl(mark_price)
            data["unrealized_pnl_percent"] = (
                self.unrealized_pnl(mark_price) / self.notional_usdt * 100
                if self.notional_usdt
                else 0.0
            )
        return data


@dataclass(slots=True)
class Trade:
    id: int | None
    symbol: str
    side: str
    qty: float
    entry_price: float | None = None
    exit_price: float | None = None
    gross_pnl: float = 0.0
    fee_usdt: float = 0.0
    net_pnl: float = 0.0
    reason: str = ""
    entry_pattern: str = ""
    entry_confidence: float = 0.0
    entry_diagnostics: dict[str, Any] = field(default_factory=dict)
    opened_at: datetime | None = None
    closed_at: datetime | None = None

    def as_dict(self) -> dict[str, Any]:
        data = asdict(self)
        data["opened_at"] = self.opened_at.isoformat() if self.opened_at else None
        data["closed_at"] = self.closed_at.isoformat() if self.closed_at else None
        return data


@dataclass(slots=True)
class BotStatus:
    running: bool
    mode: str
    live_trading_ready: bool
    symbols: list[str]
    started_at: datetime | None
    last_loop_at: datetime | None
    message: str = ""

    def as_dict(self) -> dict[str, Any]:
        data = asdict(self)
        data["started_at"] = self.started_at.isoformat() if self.started_at else None
        data["last_loop_at"] = self.last_loop_at.isoformat() if self.last_loop_at else None
        return data
