"""
Журнал учёта сделок (Trade Journal).

Хранит все открытые и закрытые сделки, позволяет формировать отчёты.

Классы:
    Trade — одна сделка
    TradeJournal — журнал сделок (persistent JSON)

Файл хранения: reports/trades.json
"""

import json
import os
import uuid
from dataclasses import dataclass, field, asdict
from datetime import datetime
from typing import Optional


TRADES_FILE = os.path.join(
    os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
    "reports", "trades.json",
)


@dataclass
class Trade:
    """Одна сделка."""

    trade_id: str                          # уникальный UUID
    ticker: str
    figi: str
    direction: str                         # BUY / SELL
    status: str                            # OPEN / CLOSED / CANCELLED
    source: str                            # "report" / "manual"

    # Вход
    entry_price: float
    entry_price_tick: float                # округлено до шага
    entry_quantity_lots: int
    entry_quantity_shares: int
    entry_commission: float
    entry_time: str                        # ISO datetime
    entry_order_id: str = ""               # ID ордера из API

    # Выход
    exit_price: float = 0.0
    exit_price_tick: float = 0.0
    exit_commission: float = 0.0
    exit_reason: str = ""                  # "TP" / "SL" / "MANUAL"
    exit_time: str = ""
    exit_order_id: str = ""

    # Параметры сделки
    sl_price: float = 0.0
    tp_price: float = 0.0
    capital: float = 0.0
    risk_pct: float = 0.0
    confidence: int = 0

    # Расчётное
    planned_risk_rub: float = 0.0
    planned_reward_rub: float = 0.0
    planned_rr: float = 0.0

    # Фактическое
    actual_pnl_rub: float = 0.0
    actual_pnl_pct: float = 0.0
    actual_rr: float = 0.0

    closed_manually: bool = False


class TradeJournal:
    """Журнал учёта сделок."""

    def __init__(self, filepath: str = TRADES_FILE):
        self.filepath = filepath
        self._trades: dict[str, Trade] = {}
        self._load()

    def _load(self):
        if os.path.exists(self.filepath):
            with open(self.filepath, "r", encoding="utf-8") as f:
                data = json.load(f)
                # Фильтруем неизвестные поля (обратная совместимость)
                known_fields = {f.name for f in Trade.__dataclass_fields__.values()}
                for t in data.values():
                    filtered = {k: v for k, v in t.items() if k in known_fields}
                    trade = Trade(**filtered)
                    self._trades[trade.trade_id] = trade

    def _save(self):
        os.makedirs(os.path.dirname(self.filepath), exist_ok=True)
        data = {tid: asdict(t) for tid, t in self._trades.items()}
        with open(self.filepath, "w", encoding="utf-8") as f:
            json.dump(data, f, indent=2, ensure_ascii=False, default=str)

    # ── Операции ──────────────────────────────────────────

    def _validate_sl_tp(
        self,
        direction: str,
        entry_price: float,
        sl_price: float,
        tp_price: float,
    ) -> None:
        """
        Валидация SL/TP относительно направления сделки.

        Raises:
            ValueError: если SL/TP не соответствуют направлению.
        """
        if direction == "BUY":
            if sl_price > 0 and sl_price >= entry_price:
                raise ValueError(
                    f"BUY: SL ({sl_price:.2f}) должен быть НИЖЕ entry ({entry_price:.2f})"
                )
            if tp_price > 0 and tp_price <= entry_price:
                raise ValueError(
                    f"BUY: TP ({tp_price:.2f}) должен быть ВЫШЕ entry ({entry_price:.2f})"
                )
        elif direction == "SELL":
            if sl_price > 0 and sl_price <= entry_price:
                raise ValueError(
                    f"SELL: SL ({sl_price:.2f}) должен быть ВЫШЕ entry ({entry_price:.2f})"
                )
            if tp_price > 0 and tp_price >= entry_price:
                raise ValueError(
                    f"SELL: TP ({tp_price:.2f}) должен быть НИЖЕ entry ({entry_price:.2f})"
                )

    def open_trade(
        self,
        ticker: str,
        figi: str,
        direction: str,
        entry_price: float,
        entry_price_tick: float,
        quantity_lots: int,
        quantity_shares: int,
        entry_commission: float,
        sl_price: float,
        tp_price: float,
        capital: float,
        risk_pct: float,
        planned_risk: float,
        planned_reward: float,
        planned_rr: float,
        confidence: int = 0,
        source: str = "manual",
        entry_order_id: str = "",
    ) -> Trade:
        # ⚠️ КРИТИЧЕСКАЯ ВАЛИДАЦИЯ: проверка SL/TP относительно направления
        if sl_price > 0 and tp_price > 0:
            self._validate_sl_tp(direction, entry_price, sl_price, tp_price)

        trade = Trade(
            trade_id=str(uuid.uuid4()),
            ticker=ticker.upper(),
            figi=figi,
            direction=direction.upper(),
            status="OPEN",
            source=source,
            entry_price=entry_price,
            entry_price_tick=entry_price_tick,
            entry_quantity_lots=quantity_lots,
            entry_quantity_shares=quantity_shares,
            entry_commission=entry_commission,
            entry_time=datetime.now().isoformat(),
            entry_order_id=entry_order_id,
            sl_price=sl_price,
            tp_price=tp_price,
            capital=capital,
            risk_pct=risk_pct,
            confidence=confidence,
            planned_risk_rub=planned_risk,
            planned_reward_rub=planned_reward,
            planned_rr=planned_rr,
        )
        self._trades[trade.trade_id] = trade
        self._save()
        return trade

    def close_trade(
        self,
        trade_id: str,
        exit_price: float,
        exit_price_tick: float,
        exit_commission: float,
        exit_reason: str = "MANUAL",
        exit_order_id: str = "",
    ) -> Optional[Trade]:
        trade = self._trades.get(trade_id)
        if not trade:
            return None
        if trade.status != "OPEN":
            return trade

        trade.exit_price = exit_price
        trade.exit_price_tick = exit_price_tick
        trade.exit_commission = exit_commission
        trade.exit_reason = exit_reason
        trade.exit_time = datetime.now().isoformat()
        trade.exit_order_id = exit_order_id
        trade.status = "CLOSED"

        # P&L
        if trade.direction == "BUY":
            gross = (exit_price - trade.entry_price) * trade.entry_quantity_shares
        else:
            gross = (trade.entry_price - exit_price) * trade.entry_quantity_shares
        total_comm = trade.entry_commission + exit_commission
        trade.actual_pnl_rub = gross - total_comm
        trade.actual_pnl_pct = (
            trade.actual_pnl_rub / trade.capital * 100 if trade.capital else 0
        )
        trade.actual_rr = (
            abs(trade.actual_pnl_rub) / trade.planned_risk_rub
            if trade.planned_risk_rub and trade.actual_pnl_rub > 0
            else (
                -abs(trade.actual_pnl_rub) / trade.planned_risk_rub
                if trade.planned_risk_rub
                else 0
            )
        )

        self._save()
        return trade

    def cancel_trade(self, trade_id: str) -> Optional[Trade]:
        trade = self._trades.get(trade_id)
        if not trade:
            return None
        trade.status = "CANCELLED"
        self._save()
        return trade

    # ── Запросы ───────────────────────────────────────────

    def get_open_trades(self) -> list[Trade]:
        return [t for t in self._trades.values() if t.status == "OPEN"]

    def get_closed_trades(self) -> list[Trade]:
        return [t for t in self._trades.values() if t.status == "CLOSED"]

    def get_all_trades(self) -> list[Trade]:
        return sorted(self._trades.values(), key=lambda t: t.entry_time, reverse=True)

    def get_trade(self, trade_id: str) -> Optional[Trade]:
        return self._trades.get(trade_id)

    def get_open_trades_by_ticker(self, ticker: str) -> list[Trade]:
        return [
            t for t in self._trades.values()
            if t.status == "OPEN" and t.ticker == ticker.upper()
        ]

    # ── Отчёты ────────────────────────────────────────────

    def get_summary(self) -> dict:
        """Сводка по журналу."""
        open_trades = self.get_open_trades()
        closed = self.get_closed_trades()

        total_pnl = sum(t.actual_pnl_rub for t in closed)
        wins = [t for t in closed if t.actual_pnl_rub > 0]
        losses = [t for t in closed if t.actual_pnl_rub <= 0]
        win_rate = len(wins) / len(closed) * 100 if closed else 0

        total_commission = sum(
            t.entry_commission + t.exit_commission for t in closed
        )

        avg_rr = (
            sum(t.actual_rr for t in closed) / len(closed) if closed else 0
        )

        return {
            "total_trades": len(self._trades),
            "open": len(open_trades),
            "closed": len(closed),
            "cancelled": sum(1 for t in self._trades.values() if t.status == "CANCELLED"),
            "win_rate": round(win_rate, 1),
            "wins": len(wins),
            "losses": len(losses),
            "total_pnl": round(total_pnl, 2),
            "total_commission": round(total_commission, 2),
            "avg_rr": round(avg_rr, 2),
        }

    def get_report(self) -> str:
        """Текстовый отчёт для пользователя/агента."""
        summary = self.get_summary()
        open_trades = self.get_open_trades()
        closed = self.get_closed_trades()

        lines = [
            "# Журнал сделок",
            "",
            f"**Всего сделок:** {summary['total_trades']} "
            f"(открыто: {summary['open']}, закрыто: {summary['closed']}, "
            f"отменено: {summary['cancelled']})",
            "",
        ]

        if closed:
            lines.append("## Статистика")
            lines.append("")
            lines.append(f"| Показатель | Значение |")
            lines.append(f"|------------|----------|")
            lines.append(f"| P&L | {summary['total_pnl']:+.2f} ₽ |")
            lines.append(f"| Комиссий | {summary['total_commission']:.2f} ₽ |")
            lines.append(f"| Win rate | {summary['win_rate']:.1f}% ({summary['wins']}W / {summary['losses']}L) |")
            lines.append(f"| Средний RR | {summary['avg_rr']:.2f} |")
            lines.append("")

        if open_trades:
            lines.append("## Открытые позиции")
            lines.append("")
            lines.append("| ID | Тикер | Напр. | Вход | SL | TP | Лотов | Риск | Conf. | Открыта |")
            lines.append("|----|-------|:-----:|:----:|:--:|:--:|:-----:|:----:|:-----:|---------|")
            for t in sorted(open_trades, key=lambda x: x.entry_time):
                trade_id_short = t.trade_id[:8]
                lines.append(
                    f"| {trade_id_short} | {t.ticker} | {t.direction} | "
                    f"{t.entry_price_tick:.2f} | {t.sl_price:.2f} | {t.tp_price:.2f} | "
                    f"{t.entry_quantity_lots} | {t.planned_risk_rub:.0f} ₽ | "
                    f"{t.confidence}% | {t.entry_time[:16]} |"
                )
            lines.append("")

        if closed:
            lines.append("## Закрытые сделки (последние 20)")
            lines.append("")
            lines.append("| Тикер | Напр. | Вход | Выход | P&L | Причина | Закрыта |")
            lines.append("|-------|:-----:|:----:|:-----:|:---:|:-------:|---------|")
            for t in sorted(closed, key=lambda x: x.exit_time, reverse=True)[:20]:
                lines.append(
                    f"| {t.ticker} | {t.direction} | {t.entry_price_tick:.2f} | "
                    f"{t.exit_price_tick:.2f} | {t.actual_pnl_rub:+.2f} ₽ | "
                    f"{t.exit_reason} | {t.exit_time[:16]} |"
                )
            lines.append("")

        return "\n".join(lines)


# Глобальный экземпляр
journal = TradeJournal()
