#!/usr/bin/env python3
"""testnet_check.py — Verifies the OCO flow END TO END on Binance Spot TESTNET.

NO REAL MONEY (testnet = fake balance). It still sends small orders to the real
API, so it asks for explicit confirmation (skipped with --yes). Steps:

  1) Connection + authentication (reads account/balance)
  2) Symbol filters + current price
  3) Small market BUY (USDT up to the notional)
  4) Place OCO SELL (take-profit ABOVE, stop-loss BELOW)
  5) VERIFY via get_oco_status that the OCO is RESTING (pending) on the exchange
  6) Cleanup: cancel OCO + market sell the base back (flat). Skipped with --keep.

Each step is reported as PASS/FAIL; if a step blows up, cleanup is still attempted.

Setup + usage:
  cp .env.example .env          # then fill in BINANCE_TESTNET_API_KEY/SECRET
  #   keys: https://testnet.binance.vision  (sign in with GitHub -> Generate HMAC key)
  python scripts/testnet_check.py                       # BTCUSDT, ~20 USDT
  python scripts/testnet_check.py --symbol ETHUSDT --notional 25
  python scripts/testnet_check.py --keep                # leave the OCO (view it in the panel)
"""
from __future__ import annotations

import argparse
import pathlib
import sys

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

from rich.console import Console
from rich.table import Table

from coinbot.config import Mode, load_settings
from coinbot.exchange import Exchange, ExchangeError

console = Console()


def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
    p = argparse.ArgumentParser(
        prog="testnet_check.py",
        description="Binance TESTNET'te OCO akışını uçtan uca doğrular (sahte para).",
    )
    p.add_argument("--symbol", default="BTCUSDT", help="Test sembolü (varsayılan BTCUSDT).")
    p.add_argument("--notional", type=float, default=20.0,
                   help="Harcanacak yaklaşık USDT (varsayılan 20).")
    p.add_argument("--keep", action="store_true",
                   help="OCO'yu iptal etme/geri satma; testnet panelinde görmek için bırak.")
    p.add_argument("--yes", action="store_true", help="Onay sormadan çalıştır.")
    return p.parse_args(argv)


def main(argv: list[str] | None = None) -> int:
    args = _parse_args(argv)

    # Force the mode to TESTNET; if no key is present, load_settings raises a clear error.
    try:
        settings = load_settings(mode_override="testnet")
    except (RuntimeError, ValueError) as exc:
        console.print(f"[bold red]TESTNET baslatilamadi:[/] {exc}")
        console.print(
            "\n[yellow]Cözüm:[/] [bold]cp .env.example .env[/] yapip "
            "BINANCE_TESTNET_API_KEY ve BINANCE_TESTNET_API_SECRET degerlerini "
            "[link]https://testnet.binance.vision[/] adresinden alip doldurun."
        )
        return 2

    if settings.mode != Mode.TESTNET:
        console.print("[bold red]Bu arac yalnizca TESTNET modunda calisir.[/]")
        return 2

    symbol = args.symbol.upper()
    console.print(
        f"[bold]TESTNET OCO doğrulaması[/] — sembol=[cyan]{symbol}[/], "
        f"hedef notional=[cyan]{args.notional:.2f} {settings.quote_asset}[/]"
    )
    console.print("[dim]Sahte para; ancak gerçek testnet emirleri gönderilecek.[/]")
    if not args.yes:
        try:
            ans = input("Devam? (e/h) > ").strip().lower()
        except (EOFError, KeyboardInterrupt):
            ans = "h"
        if ans not in ("e", "evet", "y", "yes"):
            console.print("[yellow]İptal edildi.[/]")
            return 1

    ex = Exchange(settings)
    results: list[tuple[str, bool, str]] = []
    order_list_id = None
    bought_qty = 0.0

    def step(name: str, ok: bool, detail: str = "") -> None:
        results.append((name, ok, detail))
        mark = "[green]PASS[/]" if ok else "[red]FAIL[/]"
        console.print(f"  {mark}  {name}" + (f" — {detail}" if detail else ""))

    # 1) Connection + authentication + balance
    try:
        quote_bal = ex.get_asset_balance(settings.quote_asset)
        step("1) Bağlantı + kimlik", True, f"{settings.quote_asset} serbest bakiye={quote_bal:.2f}")
        if quote_bal < args.notional:
            console.print(
                f"[yellow]Uyari:[/] testnet {settings.quote_asset} bakiyesi ({quote_bal:.2f}) "
                f"hedef notional'dan ({args.notional:.2f}) düşük. testnet.binance.vision "
                f"panelinden faucet ile bakiye alabilirsiniz."
            )
    except ExchangeError as exc:
        step("1) Bağlantı + kimlik", False, str(exc))
        return _finish(results, ex, symbol, order_list_id, bought_qty, args)

    # 2) Filters + price
    try:
        price = ex.get_price(symbol)
        filt = ex.get_symbol_filters(symbol)
        min_notional = max(float(filt.get("min_notional", 0.0)), settings.risk.min_notional_usdt)
        # The OCO stop leg is priced at ~0.95*price, so leave some margin in the notional.
        notional = max(args.notional, min_notional / 0.9)
        step("2) Filtreler + fiyat", price > 0,
             f"fiyat={price:.2f}, tick={filt['tick_size']}, step={filt['step_size']}, "
             f"min_notional={min_notional:.2f} -> kullanilacak notional={notional:.2f}")
    except ExchangeError as exc:
        step("2) Filtreler + fiyat", False, str(exc))
        return _finish(results, ex, symbol, order_list_id, bought_qty, args)

    # 3) Market BUY
    try:
        buy = ex.market_buy(symbol, notional)
        bought_qty = float(buy["qty"])
        step("3) Market ALIM", bought_qty > 0,
             f"qty={bought_qty} @ {buy['price']:.2f} (harcanan={buy['quote_spent']:.2f})")
    except ExchangeError as exc:
        step("3) Market ALIM", False, str(exc))
        return _finish(results, ex, symbol, order_list_id, bought_qty, args)

    # 4) Place OCO SELL (TP above, stop below)
    try:
        tp = price * 1.05
        stop = price * 0.95
        oco = ex.place_oco_sell(symbol, bought_qty, tp, stop)
        order_list_id = oco["order_list_id"]
        step("4) OCO SAT yerleştir", order_list_id is not None,
             f"listId={order_list_id}, tp~{tp:.2f}, stop~{stop:.2f}, durum={oco.get('status')}")
    except ExchangeError as exc:
        step("4) OCO SAT yerleştir", False, str(exc))
        return _finish(results, ex, symbol, order_list_id, bought_qty, args)

    # 5) Is the OCO RESTING? (get_oco_status)
    try:
        status = ex.get_oco_status(symbol, order_list_id)
        is_resting = (not status.get("filled")) and (not status.get("list_done"))
        step("5) OCO borsada RESTING", is_resting,
             f"list_status={status.get('list_status')}, filled={status.get('filled')}")
    except ExchangeError as exc:
        step("5) OCO borsada RESTING", False, str(exc))

    return _finish(results, ex, symbol, order_list_id, bought_qty, args)


def _finish(results, ex, symbol, order_list_id, bought_qty, args) -> int:
    # 6) Cleanup: cancel OCO + sell the base back (flat) — unless --keep.
    if args.keep:
        console.print("[yellow]--keep verildi: OCO bırakıldı (testnet panelinde görebilirsiniz).[/]")
    else:
        if order_list_id is not None:
            try:
                ex.cancel_oco(symbol, order_list_id)
                results.append(("6a) OCO iptal", True, f"listId={order_list_id}"))
                console.print(f"  [green]PASS[/]  6a) OCO iptal — listId={order_list_id}")
            except ExchangeError as exc:
                results.append(("6a) OCO iptal", False, str(exc)))
                console.print(f"  [red]FAIL[/]  6a) OCO iptal — {exc}")
        if bought_qty > 0:
            try:
                base = ex.get_base_asset(symbol)
                free = ex.get_asset_balance(base)
                # Only sell back the amount BOUGHT in this test (don't touch the existing account balance).
                sell_qty = ex.round_qty(symbol, min(bought_qty, free))
                if sell_qty > 0:
                    sell = ex.market_sell(symbol, sell_qty)
                    results.append(("6b) Base geri sat (flat)", True,
                                    f"qty={sell['qty']} @ {sell['price']:.2f}"))
                    console.print(f"  [green]PASS[/]  6b) Base geri sat — qty={sell['qty']}")
                else:
                    console.print("  [yellow]·[/]  6b) Satılacak base yok (zaten flat).")
            except ExchangeError as exc:
                results.append(("6b) Base geri sat (flat)", False, str(exc)))
                console.print(f"  [red]FAIL[/]  6b) Base geri sat — {exc}")

    # Summary table
    table = Table(title="TESTNET OCO Doğrulama Özeti")
    table.add_column("Adım", style="cyan")
    table.add_column("Sonuç", justify="center")
    table.add_column("Detay", style="dim")
    all_ok = True
    for name, ok, detail in results:
        table.add_row(name, "[green]PASS[/]" if ok else "[red]FAIL[/]", detail)
        all_ok = all_ok and ok
    console.print(table)

    if all_ok:
        console.print("[bold green]✓ Tüm adımlar geçti: OCO akışı testnet'te doğru çalışıyor.[/]")
        return 0
    console.print("[bold red]✗ En az bir adım başarısız — yukarıdaki detaylara bakın.[/]")
    return 1


if __name__ == "__main__":
    raise SystemExit(main())
