"""Reset TOTAL del paper account: cierra TODAS las posiciones abiertas en
Alpaca, cancela TODAS las ordenes pendientes y limpia las tablas de trades
en la DB local. Usar cuando el sistema se ha ido de las manos y quieres
empezar limpio.

Este script:
  1) Cancela todas las ordenes ABIERTAS/PENDIENTES en Alpaca.
  2) Cierra (SELL market) todas las posiciones LONG en Alpaca.
  3) UPDATE open_trades SET status='closed_manual_reset' WHERE status='open'.
  4) Deduplica open_trades (mantiene la mas reciente por ticker si hubo zombies).
  5) Snapshot equity antes/despues + log detallado.
  6) Devuelve JSON con el resumen (usado por el endpoint /api/reset-paper-total).

USO CLI:
    python tools/reset_paper_total.py           # dry-run: solo lista
    python tools/reset_paper_total.py --execute # cierra todo

Modo dry-run muestra que se cerraria pero NO envia ordenes. --execute lo hace
real.
"""

from __future__ import annotations

import argparse
import json
import sqlite3
import sys
import time
from datetime import datetime, timezone
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "agents"))

from alpaca_client import (  # noqa: E402
    _build_client, fetch_snapshot, submit_market_order,
)

DB_PATH = ROOT / "data" / "Leonex.sqlite"


def _now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


def cancel_all_open_orders(client) -> tuple[int, list[dict]]:
    """Cancela todas las ordenes NO filled en Alpaca."""
    canceled = []
    try:
        from alpaca.trading.requests import GetOrdersRequest
        from alpaca.trading.enums import QueryOrderStatus
    except ImportError:
        return 0, [{"error": "alpaca_sdk_not_installed"}]
    try:
        req = GetOrdersRequest(status=QueryOrderStatus.OPEN, limit=500)
        open_orders = client.get_orders(filter=req)
    except Exception as exc:
        return 0, [{"error": f"list_orders_failed: {exc!r}"}]

    for o in open_orders or []:
        try:
            client.cancel_order_by_id(o.id)
            canceled.append({"id": str(o.id), "symbol": str(o.symbol),
                             "side": str(o.side).lower()})
        except Exception as exc:
            canceled.append({"id": str(o.id), "symbol": str(o.symbol),
                             "error": repr(exc)})
    return len(canceled), canceled


def close_all_positions(dry_run: bool = False) -> tuple[int, float, list[dict]]:
    """Envia SELL market para cada posicion LONG en Alpaca (o BUY si SHORT).
    Devuelve (n_orders, notional_total, detalle_por_ticker)."""
    snap = fetch_snapshot()
    if not snap.configured or not snap.positions:
        return 0, 0.0, []

    orders_out = []
    notional_total = 0.0
    for p in snap.positions:
        symbol = str(p.symbol)
        qty_raw = float(getattr(p, "qty", 0))
        side_raw = str(getattr(p, "side", "long")).lower()
        current_price = float(getattr(p, "current_price", 0))
        market_value = abs(qty_raw * current_price)
        notional_total += market_value
        # Cerrar LONG -> SELL. Cerrar SHORT -> BUY (raro en paper largo).
        exit_side = "sell" if "long" in side_raw else "buy"
        close_qty = abs(qty_raw)

        row = {
            "ticker": symbol,
            "qty": close_qty,
            "side_exit": exit_side,
            "entry_price": float(getattr(p, "avg_entry_price", 0)),
            "current_price": current_price,
            "market_value": market_value,
            "unrealized_pnl": float(getattr(p, "unrealized_pnl", 0)),
        }

        if dry_run:
            row["status"] = "would_close"
            orders_out.append(row)
            continue

        result = submit_market_order(symbol, close_qty, side=exit_side,
                                     time_in_force="day")
        row["status"] = result.get("status") or ("submitted"
                                                 if result.get("ok") else "rejected")
        row["order_id"] = result.get("order_id")
        row["error"] = result.get("error")
        orders_out.append(row)
        # rate-limit courtesy
        time.sleep(0.05)

    return len(orders_out), notional_total, orders_out


def clean_open_trades(dry_run: bool = False) -> dict:
    """UPDATE open_trades SET status='closed_manual_reset' WHERE status='open'.
    Y deduplica: si un ticker tiene multiples entradas OPEN, mantiene la mas
    reciente (por entry_date DESC).
    Devuelve counters."""
    if not DB_PATH.exists():
        return {"error": "db_not_found"}

    stats = {"open_before": 0, "duplicates_dedup": 0, "closed_total": 0}

    with sqlite3.connect(DB_PATH) as conn:
        cur = conn.execute(
            "SELECT COUNT(*) FROM open_trades WHERE status = 'open'"
        )
        stats["open_before"] = int(cur.fetchone()[0] or 0)

        # Contar duplicados (por ticker con status=open)
        rows = conn.execute(
            "SELECT ticker, COUNT(*) c FROM open_trades WHERE status='open' "
            "GROUP BY ticker HAVING c > 1"
        ).fetchall()
        stats["duplicates_dedup"] = sum(int(r[1]) - 1 for r in rows)

        if not dry_run:
            ts = _now_iso()
            # Cerrar TODAS las open
            conn.execute(
                "UPDATE open_trades SET status = 'closed_manual_reset', "
                "closed_at = ? WHERE status = 'open'",
                (ts,),
            )
            stats["closed_total"] = stats["open_before"]
            conn.commit()

    return stats


def activate_post_reset_pause(dry_run: bool = False) -> dict:
    """DEJA ACTIVO el PAUSE_NEW_TRADES con motivo 'reset_manual'. Asi el
    scheduler interno del bridge NO abrira posiciones automaticamente hasta
    que el usuario decida reanudar (manual reset del pause via CLI o
    endpoint). Es un guardrail post-reset."""
    if dry_run:
        return {"pause_activated": False, "dry_run": True}
    try:
        with sqlite3.connect(DB_PATH) as conn:
            conn.execute(
                "CREATE TABLE IF NOT EXISTS system_state ("
                "key TEXT PRIMARY KEY, value TEXT, updated_at TEXT)"
            )
            ts = _now_iso()
            conn.execute(
                "INSERT INTO system_state(key, value, updated_at) VALUES "
                "('pause_new_trades', 'true', ?) "
                "ON CONFLICT(key) DO UPDATE SET value=excluded.value, "
                "updated_at=excluded.updated_at",
                (ts,),
            )
            conn.execute(
                "INSERT INTO system_state(key, value, updated_at) VALUES "
                "('pause_reason', ?, ?) "
                "ON CONFLICT(key) DO UPDATE SET value=excluded.value, "
                "updated_at=excluded.updated_at",
                ("manual: reset_paper_total ejecutado; el sistema NO abrira "
                 "posiciones hasta reanudacion manual (agente_risk_monitor.py "
                 "--reset-pause o equivalente)", ts),
            )
            conn.commit()
        return {"pause_activated": True}
    except Exception as exc:
        return {"pause_activated": False, "error": repr(exc)}


def run(execute: bool = False) -> dict:
    """Ejecuta el reset (o dry-run). Devuelve dict con el resumen."""
    report = {
        "generated_at": _now_iso(),
        "mode": "execute" if execute else "dry_run",
    }

    client, _, _, err = _build_client()
    if client is None:
        report["error"] = f"alpaca_client_no_configurado: {err}"
        return report

    # 1) Snapshot antes
    snap_before = fetch_snapshot()
    report["equity_before"] = float(snap_before.account.equity) if snap_before.account else 0.0
    report["n_positions_before"] = len(snap_before.positions) if snap_before.positions else 0
    report["n_recent_orders_before"] = len(snap_before.recent_orders) if snap_before.recent_orders else 0

    # 2) Cancelar ordenes pendientes
    if execute:
        n_canceled, canceled_detail = cancel_all_open_orders(client)
    else:
        n_canceled, canceled_detail = 0, [{"dry_run": True}]
    report["orders_canceled"] = n_canceled
    report["orders_canceled_detail"] = canceled_detail

    # 3) Cerrar posiciones
    n_closed, notional, close_detail = close_all_positions(dry_run=not execute)
    report["positions_closed"] = n_closed
    report["notional_closed"] = round(notional, 2)
    report["positions_closed_detail"] = close_detail

    # 4) Limpiar open_trades
    db_stats = clean_open_trades(dry_run=not execute)
    report["db_open_trades_before"] = db_stats.get("open_before", 0)
    report["db_duplicates_dedup"] = db_stats.get("duplicates_dedup", 0)
    report["db_closed_total"] = db_stats.get("closed_total", 0)

    # 5) DEJAR PAUSE ACTIVO post-reset (guardrail)
    pause_stats = activate_post_reset_pause(dry_run=not execute)
    report["post_reset_pause"] = pause_stats

    # 6) Snapshot despues (solo si execute)
    if execute:
        time.sleep(2)  # dar tiempo a Alpaca a procesar
        snap_after = fetch_snapshot()
        report["equity_after"] = float(snap_after.account.equity) if snap_after.account else 0.0
        report["n_positions_after"] = len(snap_after.positions) if snap_after.positions else 0

    return report


def main() -> int:
    parser = argparse.ArgumentParser(
        description="Reset TOTAL del paper account de Leonex")
    parser.add_argument("--execute", action="store_true",
                        help="Cierra TODAS las posiciones y limpia DB. "
                             "Sin este flag es dry-run.")
    args = parser.parse_args()

    report = run(execute=args.execute)
    print(json.dumps(report, indent=2, ensure_ascii=False, default=str))
    if "error" in report and report.get("error"):
        return 1
    return 0


if __name__ == "__main__":
    sys.exit(main())
