"""Paper trader: loop principale che monitora, segnala e opera su Deribit testnet."""
from __future__ import annotations

import json
import time
from datetime import datetime, timedelta, timezone
from pathlib import Path

import pandas as pd

from src.live.cerbero_client import CerberoClient
from src.live.signal_engine import SignalEngine
from src.live.telegram_notifier import notify_event

LOG_DIR = Path(__file__).resolve().parents[2] / "data" / "paper_trades"
INSTRUMENT = "ETH_USDC-PERPETUAL"
TRAIN_INSTRUMENT = "ETH-PERPETUAL"
CURRENCY = "USDC"
RESOLUTION = "15"
LEVERAGE = 3
POSITION_PCT = 0.15
HOLD_BARS = 3
POLL_SECONDS = 60
LOOKBACK_DAYS = 60
TRAIN_LOOKBACK_DAYS = 365
VIRTUAL_CAPITAL = 1000.0  # simula capitale reale, ignora balance testnet


class PaperTrader:
    def __init__(self):
        self.client = CerberoClient()
        self.engine = SignalEngine(bb_w=14, sq_thr=0.8, ml_thr=0.70)

        self.virtual_capital = VIRTUAL_CAPITAL
        self.in_position = False
        self.position_entry_time: datetime | None = None
        self.position_direction: str | None = None
        self.position_entry_price: float = 0
        self.position_size: float = 0
        self.bars_held = 0
        self.last_bar_ts: int = 0

        LOG_DIR.mkdir(parents=True, exist_ok=True)
        self.log_path = LOG_DIR / f"trades_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jsonl"
        self.status_path = LOG_DIR / "status.json"

    def log(self, event: str, data: dict | None = None):
        entry = {
            "timestamp": datetime.now(timezone.utc).isoformat(),
            "event": event,
            **(data or {}),
        }
        with open(self.log_path, "a") as f:
            f.write(json.dumps(entry) + "\n")
        print(f"  [{entry['timestamp'][:19]}] {event}: {json.dumps(data or {})}")
        notify_event(event, data)

    def save_status(self):
        status = {
            "virtual_capital": round(self.virtual_capital, 2),
            "in_position": self.in_position,
            "direction": self.position_direction,
            "entry_price": self.position_entry_price,
            "position_size": self.position_size,
            "entry_time": self.position_entry_time.isoformat() if self.position_entry_time else None,
            "bars_held": self.bars_held,
            "last_update": datetime.now(timezone.utc).isoformat(),
        }
        with open(self.status_path, "w") as f:
            json.dump(status, f, indent=2)

    def fetch_candles(self, days: int = LOOKBACK_DAYS, instrument: str | None = None) -> pd.DataFrame:
        end = datetime.now(timezone.utc)
        start = end - timedelta(days=days)
        candles = self.client.get_historical(
            instrument or TRAIN_INSTRUMENT,
            start.strftime("%Y-%m-%d"),
            end.strftime("%Y-%m-%d"),
            RESOLUTION,
        )
        if not candles:
            return pd.DataFrame()
        df = pd.DataFrame(candles)
        df["timestamp"] = df["timestamp"].astype("int64")
        df = df.sort_values("timestamp").reset_index(drop=True)
        return df

    def train_model(self):
        self.log("TRAINING", {"lookback_days": TRAIN_LOOKBACK_DAYS, "instrument": TRAIN_INSTRUMENT})
        df = self.fetch_candles(TRAIN_LOOKBACK_DAYS, TRAIN_INSTRUMENT)
        if df.empty:
            self.log("TRAINING_FAILED", {"reason": "no data"})
            return False
        result = self.engine.train(df, lookahead=HOLD_BARS)
        self.log("TRAINING_DONE", result)
        return "error" not in result

    def open_position(self, direction: str, signal: dict):
        ticker = self.client.get_ticker(INSTRUMENT)
        price = ticker["last_price"]

        notional = self.virtual_capital * POSITION_PCT * LEVERAGE
        amount = round(notional / price, 3)
        amount = max(amount, 0.001)

        side = "buy" if direction == "buy" else "sell"

        self.log("OPENING", {
            "side": side,
            "amount": amount,
            "price": price,
            "virtual_capital": round(self.virtual_capital, 2),
            "notional": round(notional, 2),
            "signal": signal,
        })

        try:
            result = self.client.place_order(
                instrument=INSTRUMENT,
                side=side,
                amount=amount,
                order_type="market",
                leverage=LEVERAGE,
                label="pythagoras-squeeze",
            )
            self.in_position = True
            self.position_direction = side
            self.position_entry_price = price
            self.position_size = amount
            self.position_entry_time = datetime.now(timezone.utc)
            self.bars_held = 0
            self.log("OPENED", {"order_result": result})
        except Exception as e:
            self.log("OPEN_FAILED", {"error": str(e)})

    def close_current_position(self, reason: str):
        if not self.in_position:
            return

        ticker = self.client.get_ticker(INSTRUMENT)
        exit_price = ticker["last_price"]

        if self.position_direction == "buy":
            trade_pnl = (exit_price - self.position_entry_price) * self.position_size
        else:
            trade_pnl = (self.position_entry_price - exit_price) * self.position_size

        fee = self.position_size * (self.position_entry_price + exit_price) * 0.001
        net_pnl = trade_pnl - fee
        pnl_pct = net_pnl / self.virtual_capital * 100

        self.log("CLOSING", {
            "reason": reason,
            "entry_price": self.position_entry_price,
            "exit_price": exit_price,
            "size": self.position_size,
            "trade_pnl": round(trade_pnl, 2),
            "fee": round(fee, 2),
            "net_pnl": round(net_pnl, 2),
            "pnl_pct": round(pnl_pct, 3),
            "bars_held": self.bars_held,
            "capital_before": round(self.virtual_capital, 2),
        })

        try:
            result = self.client.close_position(INSTRUMENT)
            self.virtual_capital += net_pnl
            self.log("CLOSED", {
                "result": result,
                "net_pnl": round(net_pnl, 2),
                "pnl_pct": round(pnl_pct, 3),
                "virtual_capital": round(self.virtual_capital, 2),
            })
        except Exception as e:
            self.log("CLOSE_FAILED", {"error": str(e)})

        self.in_position = False
        self.position_direction = None
        self.position_entry_price = 0
        self.position_size = 0
        self.position_entry_time = None
        self.bars_held = 0

    def check_position_exit(self, df: pd.DataFrame):
        if not self.in_position:
            return

        current_ts = df["timestamp"].iloc[-1]
        if current_ts > self.last_bar_ts:
            self.bars_held += 1
            self.last_bar_ts = current_ts

        if self.bars_held >= HOLD_BARS:
            self.close_current_position("hold_limit")
            return

        price = df["close"].iloc[-1]
        if self.position_direction == "buy":
            pnl_pct = (price - self.position_entry_price) / self.position_entry_price
        else:
            pnl_pct = (self.position_entry_price - price) / self.position_entry_price

        if pnl_pct <= -0.02:
            self.close_current_position("stop_loss_2pct")

    def run_once(self) -> str:
        """Esegui un singolo ciclo. Ritorna lo stato."""
        df = self.fetch_candles(LOOKBACK_DAYS, TRAIN_INSTRUMENT)
        if df.empty:
            return "no_data"

        if self.in_position:
            self.check_position_exit(df)
            self.save_status()
            if self.in_position:
                return f"in_position_{self.position_direction}_bar{self.bars_held}"
            return "position_closed"

        signal = self.engine.check_signal(df)
        if signal:
            self.log("SIGNAL", signal)
            self.open_position(signal["direction"], signal)
            self.save_status()
            return f"signal_{signal['direction']}"

        self.save_status()
        return "watching"

    def run(self, retrain_hours: int = 24):
        """Loop principale."""
        print("=" * 60)
        print(f"  PAPER TRADER — {INSTRUMENT} (margine {CURRENCY})")
        print(f"  Segnali da: {TRAIN_INSTRUMENT} {RESOLUTION}m")
        print(f"  Leva: {LEVERAGE}x, Position: {POSITION_PCT*100:.0f}%, Hold: {HOLD_BARS} barre")
        print(f"  Poll: ogni {POLL_SECONDS}s")
        print(f"  Log: {self.log_path}")
        print("=" * 60)

        account = self.client.get_account_summary()
        self.log("STARTUP", {
            "virtual_capital": self.virtual_capital,
            "testnet_equity": account["equity"],
            "testnet": account.get("testnet", True),
        })

        if not self.train_model():
            print("Training fallito. Uscita.")
            return

        last_train = datetime.now(timezone.utc)

        while True:
            try:
                now = datetime.now(timezone.utc)
                if (now - last_train).total_seconds() > retrain_hours * 3600:
                    self.train_model()
                    last_train = now

                status = self.run_once()
                if status != "watching":
                    print(f"  → {status}")

            except KeyboardInterrupt:
                self.log("SHUTDOWN", {"reason": "keyboard"})
                if self.in_position:
                    self.close_current_position("shutdown")
                break
            except Exception as e:
                self.log("ERROR", {"error": str(e)})
                print(f"  ERRORE: {e}")

            time.sleep(POLL_SECONDS)


if __name__ == "__main__":
    trader = PaperTrader()
    trader.run()
