"""
Deploy orchestrator — unified CLI for backtest → paper → live pipeline.

Commands:
  backtest --strategy <name> [--fast|--full] [--interval 1h]
  paper    --strategy <name> [--duration 3600]
  live     --strategy <name> [--testnet|--mainnet]
  list     List all registered strategies and backtest results.
"""
from __future__ import annotations

import argparse
import asyncio
import json
import logging
import os
import sys
from datetime import datetime
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

logging.basicConfig(level=logging.INFO, format="%(asctime)s [deploy] %(message)s", datefmt="%H:%M:%S")
logger = logging.getLogger("ftdt-deploy")

RESULTS_DIR = Path(__file__).resolve().parent.parent / "backtests" / "results"
RESULTS_DIR.mkdir(parents=True, exist_ok=True)

STRATEGY_REGISTRY = {
    "pairs": {
        "name": "Pairs Trading",
        "description": "BTC/ETH ratio Z-score mean reversion",
        "class": "strategies.nt.pairs_trading_nt.PairsTradingNT",
    },
    "hurst_vpin": {
        "name": "Hurst VPIN",
        "description": "Hurst exponent regime filter + VPIN flow imbalance",
        "class": "strategies.nt.hurst_vpin_nt.HurstVPINNT",
    },
    "as_mm": {
        "name": "Avellaneda-Stoikov",
        "description": "Stochastic control market making with inventory risk",
        "class": "strategies.nt.as_mm_nt.ASMarketMakingNT",
    },
    "obi": {
        "name": "Order Book Imbalance",
        "description": "Volume-weighted bid/ask skew — enters when L2 imbalance heavy",
        "class": "strategies.nt.obi_nt.OBINT",
    },
    "grid_mm": {
        "name": "Grid Market Making",
        "description": "Symmetric grid of limit orders around mid — captures oscillation",
        "class": "strategies.nt.grid_mm_nt.GridMMNT",
    },
    "composite_mm": {
        "name": "Composite Market Making",
        "description": "Weighted ensemble of OBI + A-S + Hurst/VPIN signals",
        "class": "strategies.nt.composite_mm_nt.CompositeMMNT",
    },
    "iceberg": {
        "name": "Iceberg Detection",
        "description": "Volume spike detection — follows whale accumulation patterns",
        "class": "strategies.nt.iceberg_nt.IcebergNT",
    },
    "funding_arb": {
        "name": "Funding Rate Arb",
        "description": "Delta-neutral carry — collect funding payments",
        "class": None,
    },
    "momentum": {
        "name": "Momentum Breakout",
        "description": "Bollinger band breakout on trending instruments",
        "class": None,
    },
    "mean_rev": {
        "name": "Mean Reversion",
        "description": "VWAP deviation oscillator",
        "class": None,
    },
}


class DeployOrchestrator:
    """Unified deployment pipeline."""

    @staticmethod
    def cmd_backtest(args):
        from backtests.vbt_runner import VBTBacktestRunner
        from backtests.nt_runner import NTBacktestRunner
        from framework.instruments import HyperliquidInstrumentCatalog

        strategy_key = args.strategy
        strategy_info = STRATEGY_REGISTRY.get(strategy_key)
        if not strategy_info:
            print(f"Unknown strategy: {strategy_key}")
            print(f"Available: {list(STRATEGY_REGISTRY.keys())}")
            return

        # Quick VectorBT backtest
        if not args.nt_only:
            print(f"\n{'='*60}")
            print(f"  VectorBT Backtest: {strategy_info['name']}")
            print(f"{'='*60}")
            runner = VBTBacktestRunner()
            result = runner.run_strategy(
                strategy=strategy_key,
                interval=args.interval,
                testnet=args.testnet,
            )
            if result:
                _save_result(strategy_key, "vbt", result)

        # Full NautilusTrader backtest
        if not args.vbt_only:
            print(f"\n{'='*60}")
            print(f"  NautilusTrader Backtest: {strategy_info['name']}")
            print(f"{'='*60}")
            catalog = HyperliquidInstrumentCatalog(testnet=args.testnet)
            runner = NTBacktestRunner()
            result = runner.run_backtest(
                strategy=strategy_key,
                interval=args.interval,
                instruments=catalog.load(),
            )
            if result:
                _save_result(strategy_key, "nt", result)

    @staticmethod
    def cmd_paper(args):
        from framework.data import HyperliquidDataProvider
        from framework.execution import PaperExecutionProvider
        from framework.config import StrategyConfig

        strategy_key = args.strategy
        strategy_info = STRATEGY_REGISTRY.get(strategy_key)
        if not strategy_info:
            print(f"Unknown strategy: {strategy_key}")
            return

        print(f"\n{'='*60}")
        print(f"  Paper Trading: {strategy_info['name']}")
        print(f"  Duration: {args.duration}s | Mainnet data")
        print(f"{'='*60}")

        provider = HyperliquidDataProvider(testnet=False)
        execution = PaperExecutionProvider()

        # Determine coin from strategy
        coin_map = {"pairs": "ETH", "hurst_vpin": "BTC", "as_mm": "BTC",
                     "obi": "BTC", "funding_arb": "BTC", "momentum": "ETH"}
        coin = args.coin or coin_map.get(strategy_key, "BTC")

        async def _run():
            start = asyncio.get_event_loop().time()
            while asyncio.get_event_loop().time() - start < args.duration:
                try:
                    prices = provider.fetch_mark_prices()
                    mark = prices.get(coin, 0)
                    if mark > 0:
                        # Simulate a signal check each tick
                        _tick(strategy_key, coin, mark, provider, execution)
                    await asyncio.sleep(1)
                except Exception as e:
                    logger.warning("Paper loop error: %s", e)
                    await asyncio.sleep(5)

        asyncio.run(_run())

    @staticmethod
    def cmd_live(args):
        from framework.execution import HyperliquidExecutionProvider

        strategy_key = args.strategy
        strategy_info = STRATEGY_REGISTRY.get(strategy_key)
        if not strategy_info:
            print(f"Unknown strategy: {strategy_key}")
            return

        use_testnet = not args.mainnet
        env = "testnet" if use_testnet else "mainnet"

        private_key = os.environ.get(f"HYPERLIQUID_{env.upper()}_PK")
        if not private_key:
            env_file = Path(__file__).resolve().parent.parent / ".env"
            if env_file.exists():
                for line in env_file.read_text().splitlines():
                    key = f"HYPERLIQUID_{env.upper()}_PK"
                    if line.startswith(f"{key}="):
                        private_key = line.split("=", 1)[1].strip()
                        break

        if not private_key:
            print(f"ERROR: HYPERLIQUID_{env.upper()}_PK not set in .env or environment")
            return

        if not use_testnet:
            resp = input(f"\n⚠️  LIVE MAINNET for {strategy_key}. Confirm? (yes/no): ")
            if resp.lower() != "yes":
                print("Aborted.")
                return

        provider = HyperliquidExecutionProvider(private_key=private_key, testnet=use_testnet)
        print(f"\n{'='*60}")
        print(f"  LIVE {env.upper()}: {strategy_info['name']}")
        print(f"  Wallet: {provider.address}")
        print(f"{'='*60}")

        # Cancel existing orders
        provider.cancel_all()
        print("Run with Ctrl+C to stop. Existing node.py/paper_trader.py unaffected.")
        print("This is a standalone execution — for prod monitoring use the existing live node.")

    @staticmethod
    def cmd_list(args):
        print(f"\n{'='*60}")
        print("  Registered Strategies")
        print(f"{'='*60}")
        for key, info in STRATEGY_REGISTRY.items():
            ported = "✅" if info["class"] else "⏳"
            print(f"  {ported} {key:15s} {info['name']:30s} {info['description']}")
        print()

        # List backtest results
        results = sorted(RESULTS_DIR.glob("*.json"), key=os.path.getmtime, reverse=True)
        if results:
            print(f"{'='*60}")
            print("  Backtest Results")
            print(f"{'='*60}")
            for r in results[:10]:
                mtime = datetime.fromtimestamp(os.path.getmtime(r)).strftime("%Y-%m-%d %H:%M")
                size_kb = os.path.getsize(r) / 1024
                print(f"  {r.name:50s} {size_kb:6.1f}KB  {mtime}")
            if len(results) > 10:
                print(f"  ... and {len(results) - 10} more")


def _save_result(strategy_key: str, engine: str, result: dict):
    ts = datetime.now().strftime("%Y%m%d-%H%M%S")
    path = RESULTS_DIR / f"{strategy_key}_{engine}_{ts}.json"
    with open(path, "w") as f:
        json.dump(result, f, indent=2, default=str)
    print(f"  Saved: {path.name}")
    if "sharpe" in result:
        print(f"  Sharpe: {result['sharpe']:.2f} | DD: {result.get('max_drawdown_pct', 0):.1f}% | Win: {result.get('win_rate', 0):.0%}")


def _tick(strategy_key: str, coin: str, mark: float, provider, execution):
    """Single tick of paper trading logic — placeholder for full strategy logic."""
    # Load strategy module dynamically
    strategy_class_path = STRATEGY_REGISTRY.get(strategy_key, {}).get("class")
    if not strategy_class_path:
        return

    module_path, class_name = strategy_class_path.rsplit(".", 1)
    import importlib
    try:
        mod = importlib.import_module(module_path)
        strategy_cls = getattr(mod, class_name)

        # Instantiate if not already cached
        if not hasattr(_tick, "_instances"):
            _tick._instances = {}
        if strategy_key not in _tick._instances:
            from framework.config import StrategyConfig
            cfg = StrategyConfig(
                name=STRATEGY_REGISTRY[strategy_key]["name"],
                instrument=f"{coin}-USD-PERP",
                asset=coin,
                allocation=10000.0,
                order_size=0.001,
                testnet=False,  # paper uses mainnet data
            )
            _tick._instances[strategy_key] = strategy_cls(cfg)

        strat = _tick._instances[strategy_key]
        sig = strat.compute_signal(price=mark)
        if sig:
            # Paper execution
            from framework.execution import PaperExecutionProvider as Pep
            pep = Pep()
            cloid = pep.submit(
                coin=coin,
                side="BUY" if "BUY" in sig.get("signal", "").upper() else "SELL",
                size=cfg.order_size,
                price=mark,
                fee_model=cfg.fee_model,
                mark_price=mark,
            )
            logger.info("Paper signal: %s → %s | fill=%s", sig["signal"], cloid, mark)
    except Exception as e:
        logger.warning("Tick error for %s: %s", strategy_key, e)


def main():
    parser = argparse.ArgumentParser(description="FTDT Quant Lab — Deploy Orchestrator")
    sub = parser.add_subparsers(dest="command", help="Command")

    # backtest
    bt = sub.add_parser("backtest", help="Run backtest (VectorBT + NautilusTrader)")
    bt.add_argument("--strategy", "-s", required=True, help="Strategy key (pairs, hurst_vpin, as_mm, etc.)")
    bt.add_argument("--fast", dest="vbt_only", action="store_true", help="VectorBT quick backtest only")
    bt.add_argument("--full", dest="nt_only", action="store_true", help="NautilusTrader full backtest only")
    bt.add_argument("--interval", default="1h", help="Candle interval (1m, 5m, 15m, 1h, 4h, 1d)")
    bt.add_argument("--testnet", action="store_true", default=False, help="Use testnet data")

    # paper
    pp = sub.add_parser("paper", help="Run paper trading simulation")
    pp.add_argument("--strategy", "-s", required=True, help="Strategy key")
    pp.add_argument("--duration", type=int, default=3600, help="Duration in seconds (default: 3600)")
    pp.add_argument("--coin", help="Override trading coin (default: strategy default)")

    # live
    ll = sub.add_parser("live", help="Run live trading")
    ll.add_argument("--strategy", "-s", required=True, help="Strategy key")
    ll.add_argument("--testnet", action="store_true", default=True, help="Use testnet (default)")
    ll.add_argument("--mainnet", action="store_true", help="Use mainnet")

    # list
    sub.add_parser("list", help="List registered strategies and results")

    args = parser.parse_args()
    if not args.command:
        parser.print_help()
        return

    orch = DeployOrchestrator()
    getattr(orch, f"cmd_{args.command}")(args)


if __name__ == "__main__":
    main()
