"""CLI: python -m horizon run strategy.py"""

from __future__ import annotations

import importlib.util
import logging
import os
import sqlite3
import sys
from datetime import datetime, timezone

import click


@click.group()
def cli():
    """Horizon - Prediction Market Trading SDK."""
    pass


@cli.command()
@click.argument("strategy_file")
@click.option("--mode", default=None, help="Trading mode: paper or live")
@click.option("--dashboard/--no-dashboard", default=None, help="Show TUI dashboard")
@click.option(
    "--log-level",
    default="info",
    type=click.Choice(["debug", "info", "warn", "error"], case_sensitive=False),
    help="Log level for both Python and Rust (via HORIZON_LOG env var)",
)
def run(strategy_file: str, mode: str | None, dashboard: bool | None, log_level: str):
    """Run a strategy file.

    The strategy file should call hz.run() which blocks until interrupted.
    CLI options override the strategy's hz.run() arguments.
    """
    # Configure Python logging
    logging.basicConfig(
        level=getattr(logging, log_level.upper()),
        format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
        datefmt="%H:%M:%S",
    )

    # Set Rust tracing level via env var (before importing _horizon)
    os.environ.setdefault("HORIZON_LOG", log_level.lower())

    # Set CLI overrides before loading the strategy module
    import horizon.strategy as strategy_mod

    if mode is not None:
        strategy_mod._cli_overrides["mode"] = mode
    if dashboard is not None:
        strategy_mod._cli_overrides["dashboard"] = dashboard

    spec = importlib.util.spec_from_file_location("strategy", strategy_file)
    if spec is None or spec.loader is None:
        click.echo(f"Error: cannot load {strategy_file}", err=True)
        sys.exit(1)

    module = importlib.util.module_from_spec(spec)
    spec.loader.exec_module(module)


def _resolve_db_path(db: str) -> str:
    """Resolve the database path from argument or HORIZON_DB env var."""
    if db:
        return db
    return os.environ.get("HORIZON_DB", "./horizon.db")


def _format_ts(ts: float) -> str:
    """Format a UNIX timestamp as a human-readable string."""
    if ts <= 0:
        return "N/A"
    return datetime.fromtimestamp(ts, tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S")


@cli.command()
@click.option("--db", default="", help="Path to Horizon database (or set HORIZON_DB)")
@click.option("--limit", default=50, help="Number of records to show")
@click.option("--market", default=None, help="Filter by market ID")
def fills(db: str, limit: int, market: str | None):
    """Show recent fills from the database."""
    db_path = _resolve_db_path(db)
    if not os.path.exists(db_path):
        click.echo(f"Database not found: {db_path}", err=True)
        sys.exit(1)

    conn = sqlite3.connect(db_path)
    try:
        if market:
            rows = conn.execute(
                "SELECT fill_id, order_id, market_id, side, order_side, "
                "price, size, fee, timestamp "
                "FROM fills WHERE market_id = ? ORDER BY timestamp DESC LIMIT ?",
                (market, limit),
            ).fetchall()
        else:
            rows = conn.execute(
                "SELECT fill_id, order_id, market_id, side, order_side, "
                "price, size, fee, timestamp "
                "FROM fills ORDER BY timestamp DESC LIMIT ?",
                (limit,),
            ).fetchall()

        total = conn.execute("SELECT COUNT(*) FROM fills").fetchone()[0]
    finally:
        conn.close()

    if not rows:
        click.echo("No fills found.")
        return

    click.echo(f"Fills ({len(rows)} of {total} total):\n")
    click.echo(
        f"{'Time':<20} {'Market':<22} {'Side':<5} {'Action':<5} "
        f"{'Price':>8} {'Size':>8} {'Fee':>8}"
    )
    click.echo("-" * 80)
    for row in rows:
        fill_id, order_id, market_id, side, order_side, price, size, fee, ts = row
        click.echo(
            f"{_format_ts(ts):<20} {market_id[:21]:<22} {side:<5} {order_side:<5} "
            f"{price:>8.4f} {size:>8.1f} {fee:>8.4f}"
        )


@cli.command()
@click.option("--db", default="", help="Path to Horizon database (or set HORIZON_DB)")
def positions(db: str):
    """Show latest position snapshot from the database."""
    db_path = _resolve_db_path(db)
    if not os.path.exists(db_path):
        click.echo(f"Database not found: {db_path}", err=True)
        sys.exit(1)

    conn = sqlite3.connect(db_path)
    try:
        batch_ts = conn.execute(
            "SELECT MAX(snapshot_batch) FROM position_snapshots"
        ).fetchone()[0]

        if batch_ts is None:
            click.echo("No position snapshots found.")
            return

        rows = conn.execute(
            "SELECT market_id, side, size, avg_entry_price, "
            "realized_pnl, unrealized_pnl, token_id "
            "FROM position_snapshots WHERE snapshot_batch = ?",
            (batch_ts,),
        ).fetchall()
    finally:
        conn.close()

    click.echo(f"Position snapshot from {_format_ts(batch_ts)}:\n")
    click.echo(
        f"{'Market':<22} {'Side':<5} {'Size':>8} {'AvgPrice':>10} "
        f"{'Realized':>10} {'Unrealized':>10} {'Total':>10}"
    )
    click.echo("-" * 80)
    total_realized = 0.0
    total_unrealized = 0.0
    for row in rows:
        market_id, side, size, avg_price, realized, unrealized, token_id = row
        total = realized + unrealized
        total_realized += realized
        total_unrealized += unrealized
        click.echo(
            f"{market_id[:21]:<22} {side:<5} {size:>8.1f} {avg_price:>10.4f} "
            f"${realized:>9.2f} ${unrealized:>9.2f} ${total:>9.2f}"
        )
    click.echo("-" * 80)
    click.echo(
        f"{'TOTAL':<37} {'':>8} {'':>10} "
        f"${total_realized:>9.2f} ${total_unrealized:>9.2f} "
        f"${total_realized + total_unrealized:>9.2f}"
    )


@cli.command()
@click.option("--db", default="", help="Path to Horizon database (or set HORIZON_DB)")
@click.option("--limit", default=50, help="Number of records to show")
@click.option("--open-only/--all", default=True, help="Show only open orders")
def orders(db: str, limit: int, open_only: bool):
    """Show orders from the database."""
    db_path = _resolve_db_path(db)
    if not os.path.exists(db_path):
        click.echo(f"Database not found: {db_path}", err=True)
        sys.exit(1)

    conn = sqlite3.connect(db_path)
    try:
        if open_only:
            rows = conn.execute(
                "SELECT order_id, market_id, side, order_side, price, size, "
                "filled_size, status, status_reason, recorded_at "
                "FROM order_events "
                "WHERE id IN (SELECT MAX(id) FROM order_events GROUP BY order_id) "
                "AND status IN ('new','submitted','accepted','partially_filled') "
                "ORDER BY recorded_at DESC LIMIT ?",
                (limit,),
            ).fetchall()
        else:
            rows = conn.execute(
                "SELECT order_id, market_id, side, order_side, price, size, "
                "filled_size, status, status_reason, recorded_at "
                "FROM order_events "
                "WHERE id IN (SELECT MAX(id) FROM order_events GROUP BY order_id) "
                "ORDER BY recorded_at DESC LIMIT ?",
                (limit,),
            ).fetchall()
    finally:
        conn.close()

    label = "open" if open_only else "all"
    if not rows:
        click.echo(f"No {label} orders found.")
        return

    click.echo(f"Orders ({len(rows)} {label}):\n")
    click.echo(
        f"{'Time':<20} {'Order ID':<12} {'Market':<18} {'Side':<5} "
        f"{'Action':<5} {'Price':>8} {'Size':>6} {'Filled':>6} {'Status':<16}"
    )
    click.echo("-" * 100)
    for row in rows:
        oid, mid, side, osid, price, size, filled, status, reason, ts = row
        status_str = status
        if reason:
            status_str += f" ({reason[:20]})"
        click.echo(
            f"{_format_ts(ts):<20} {oid[:11]:<12} {mid[:17]:<18} {side:<5} "
            f"{osid:<5} {price:>8.4f} {size:>6.1f} {filled:>6.1f} {status_str:<16}"
        )


if __name__ == "__main__":
    cli()
