#!/usr/bin/env python3
from __future__ import annotations

from datetime import datetime, timedelta
from typing import Optional

import pytz
import typer
from rich import box as rbox
from rich.console import Console
from rich.prompt import Confirm, FloatPrompt, IntPrompt, Prompt
from rich.table import Table

from .core.db import get_conn, init_db
from .core.journal import close_bet, compute_clv, log_bet, settle_bet
from .core.report import show_report, show_status

app = typer.Typer(help="betlab — NBA betting journal", add_completion=False)
console = Console()
ET = pytz.timezone("US/Eastern")

MARKETS = [
    ("player_pts_over",  "Player Points Over"),
    ("player_pts_under", "Player Points Under"),
    ("player_reb_over",  "Player Rebounds Over"),
    ("player_reb_under", "Player Rebounds Under"),
    ("player_ast_over",  "Player Assists Over"),
    ("player_ast_under", "Player Assists Under"),
    ("player_fg3m_over", "Player 3PM Over"),
    ("player_fg3m_under","Player 3PM Under"),
    ("player_stl_over",  "Player Steals Over"),
    ("player_blk_over",  "Player Blocks Over"),
    ("player_tov_over",  "Player Turnovers Over"),
    ("player_pra_over",  "Player PRA Over"),
    ("player_pra_under", "Player PRA Under"),
    ("player_pr_over",   "Player PR Over"),
    ("player_pa_over",   "Player PA Over"),
    ("total_over",       "Game Total Over"),
    ("total_under",      "Game Total Under"),
    ("spread_home",      "Spread — Home"),
    ("spread_away",      "Spread — Away"),
    ("moneyline_home",   "Moneyline — Home"),
    ("moneyline_away",   "Moneyline — Away"),
    ("other",            "Other (free text)"),
]

_MARKET_META: dict[str, tuple[str, str]] = {
    "player_pts_over":  ("PTS", "over"),
    "player_pts_under": ("PTS", "under"),
    "player_reb_over":  ("REB", "over"),
    "player_reb_under": ("REB", "under"),
    "player_ast_over":  ("AST", "over"),
    "player_ast_under": ("AST", "under"),
    "player_fg3m_over": ("FG3M", "over"),
    "player_fg3m_under":("FG3M", "under"),
    "player_stl_over":  ("STL", "over"),
    "player_blk_over":  ("BLK", "over"),
    "player_tov_over":  ("TOV", "over"),
    "player_pra_over":  ("PRA", "over"),
    "player_pra_under": ("PRA", "under"),
    "player_pr_over":   ("PR", "over"),
    "player_pa_over":   ("PA", "over"),
}


@app.command()
def init():
    """Initialize the database."""
    init_db()
    console.print("[green]✓[/green] Database ready at ~/.betlab/betlab.db")


@app.command()
def log():
    """Interactively log a new bet."""
    today_et = datetime.now(ET).strftime("%Y-%m-%d")
    date = Prompt.ask("Game date", default=today_et)

    # Try to show today's games
    game_id: str | None = None
    team_home: str | None = None
    team_away: str | None = None
    event_desc: str | None = None

    try:
        from .sports.nba.client import get_games_on_date
        games = get_games_on_date(date)
        if games:
            console.print(f"\n[bold]Games on {date}[/bold]")
            for i, g in enumerate(games, 1):
                console.print(f"  {i:2}. {g['team_away']} @ {g['team_home']}  {g['status']}")
            console.print(f"  {len(games)+1:2}. Enter manually")
            pick = IntPrompt.ask("Pick game", default=len(games) + 1)
            if 1 <= pick <= len(games):
                g = games[pick - 1]
                game_id = g["game_id"]
                team_home = g["team_home"]
                team_away = g["team_away"]
                event_desc = f"{team_away} @ {team_home}"
    except Exception as e:
        console.print(f"[dim]Could not fetch games: {e}[/dim]")

    if not event_desc:
        event_desc = Prompt.ask("Event (e.g. BOS @ LAL)")
        parts = event_desc.replace(" @ ", "@").split("@")
        if len(parts) == 2:
            team_away = parts[0].strip()
            team_home = parts[1].strip()

    # Market
    console.print("\n[bold]Market[/bold]")
    for i, (_, label) in enumerate(MARKETS, 1):
        console.print(f"  {i:2}. {label}")
    market_idx = IntPrompt.ask("Pick") - 1
    if not 0 <= market_idx < len(MARKETS):
        console.print("[red]Invalid choice.[/red]")
        raise typer.Exit(1)
    market_key = MARKETS[market_idx][0]

    player_name: str | None = None
    stat_type: str | None = None
    direction: str | None = None
    line: float | None = None
    selection: str

    if market_key in _MARKET_META:
        stat_type, direction = _MARKET_META[market_key]
        player_name = Prompt.ask("Player name")
        line = FloatPrompt.ask("Line")
        ud = "O" if direction == "over" else "U"
        selection = f"{player_name} {stat_type} {ud}{line}"
    elif market_key in ("total_over", "total_under"):
        line = FloatPrompt.ask("Total")
        direction = "over" if "over" in market_key else "under"
        ud = "O" if direction == "over" else "U"
        selection = f"Total {ud}{line}"
    elif "spread" in market_key:
        line = FloatPrompt.ask("Spread (e.g. -5.5)")
        side = team_home if "home" in market_key else team_away
        selection = f"{side or ('Home' if 'home' in market_key else 'Away')} {line:+.1f}"
        direction = "over"  # home/away perspective
    elif "moneyline" in market_key:
        side = team_home if "home" in market_key else team_away
        selection = f"{side or ('Home' if 'home' in market_key else 'Away')} ML"
    else:
        selection = Prompt.ask("Selection")

    odds_taken = FloatPrompt.ask("Odds (decimal)")
    stake = FloatPrompt.ask("Stake (RON)")

    raw_open = Prompt.ask("Opening odds (Enter to skip)", default="")
    odds_open = float(raw_open) if raw_open else None

    notes = Prompt.ask("Notes (Enter to skip)", default="") or None

    console.print(
        f"\n[bold]Confirm[/bold]  {selection} @ {odds_taken:.2f} | {stake:.1f} RON | {event_desc}"
    )
    if not Confirm.ask("Log?"):
        console.print("[dim]Cancelled.[/dim]")
        raise typer.Exit()

    bet_id = log_bet(
        game_date=date,
        event_desc=event_desc,
        market=market_key,
        selection=selection,
        odds_taken=odds_taken,
        stake=stake,
        game_id=game_id,
        team_home=team_home,
        team_away=team_away,
        player_name=player_name,
        stat_type=stat_type,
        line=line,
        direction=direction,
        odds_open=odds_open,
        notes=notes,
    )
    console.print(f"[green]✓[/green] Logged as bet [bold]#{bet_id}[/bold]")


@app.command()
def settle(
    date: Optional[str] = typer.Argument(None, help="Game date YYYY-MM-DD"),
    dry_run: bool = typer.Option(False, "--dry-run"),
):
    """Auto-grade settled bets via nba_api box scores."""
    from .sports.nba.results import settle_date

    if not date:
        date = (datetime.now(ET) - timedelta(days=1)).strftime("%Y-%m-%d")

    console.print(f"Settling bets for [bold]{date}[/bold]{'  [dim](dry run)[/dim]' if dry_run else ''}...")
    results = settle_date(date, dry_run=dry_run)

    if not results:
        console.print("[yellow]No open bets found for that date.[/yellow]")
        return

    STATUS_COLOR = {"won": "green", "lost": "red", "push": "yellow", "void": "dim"}
    for r in results:
        if r["_action"] == "skip":
            console.print(f"  [dim]#{r['id']} {r['selection'][:35]} — skipped ({r['_reason']})[/dim]")
        else:
            st = r["_status"]
            pnl = r["_payout"] - r["stake"]
            color = STATUS_COLOR.get(st, "white")
            console.print(
                f"  [{color}]#{r['id']} {r['selection'][:35]} → {st.upper()} ({pnl:+.1f} RON)[/{color}]"
            )


@app.command("settle-manual")
def settle_manual(
    bet_id: int = typer.Argument(...),
    result: str = typer.Argument(..., help="won|lost|push|void"),
):
    """Manually settle a bet."""
    result = result.lower()
    if result not in ("won", "lost", "push", "void"):
        console.print("[red]result must be won|lost|push|void[/red]")
        raise typer.Exit(1)

    with get_conn() as conn:
        row = conn.execute("SELECT * FROM bets WHERE id=?", (bet_id,)).fetchone()
    if not row:
        console.print(f"[red]Bet #{bet_id} not found.[/red]")
        raise typer.Exit(1)

    stake = row["stake"]
    odds = row["odds_taken"]
    payout = round(stake * odds, 2) if result == "won" else (stake if result in ("push", "void") else 0.0)
    settle_bet(bet_id, result, payout)

    pnl = payout - stake
    console.print(f"Bet [bold]#{bet_id}[/bold] → {result.upper()}  ({pnl:+.1f} RON)")


@app.command()
def close(
    bet_id: int = typer.Argument(...),
    closing_odds: float = typer.Option(..., "--closing-odds", "-c", help="Decimal closing odds"),
):
    """Record closing line for CLV tracking."""
    clv = close_bet(bet_id, closing_odds)
    if clv is None:
        console.print(f"[red]Bet #{bet_id} not found.[/red]")
        raise typer.Exit(1)
    direction = "beat" if clv > 0 else "missed"
    color = "green" if clv > 0 else "red"
    console.print(f"Bet [bold]#{bet_id}[/bold]  CLV [{color}]{clv:+.2f}%[/{color}]  ({direction} the close)")


@app.command()
def report(
    since: Optional[str] = typer.Option(None, help="From date YYYY-MM-DD"),
    by: str = typer.Option("market", help="Group by: market|sport|stat_type|direction|book"),
    sport: Optional[str] = typer.Option(None),
):
    """Show ROI, hit rate, CLV report."""
    show_report(since=since, sport=sport, group_by=by)


@app.command()
def status():
    """Show bankroll and open bets."""
    show_status()


@app.command()
def bets(
    open_only: bool = typer.Option(False, "--open", help="Show only open bets"),
    date: Optional[str] = typer.Option(None, help="Filter by game date"),
    limit: int = typer.Option(20, help="Max rows"),
):
    """List bets."""
    q = "SELECT * FROM bets WHERE 1=1"
    params: list = []
    if open_only:
        q += " AND status='open'"
    if date:
        q += " AND game_date=?"
        params.append(date)
    q += f" ORDER BY id DESC LIMIT {limit}"

    with get_conn() as conn:
        rows = [dict(r) for r in conn.execute(q, params).fetchall()]

    if not rows:
        console.print("[yellow]No bets found.[/yellow]")
        return

    t = Table(box=rbox.SIMPLE, header_style="bold")
    t.add_column("ID", justify="right")
    t.add_column("Date")
    t.add_column("Game", max_width=16)
    t.add_column("Selection", max_width=32)
    t.add_column("Odds", justify="right")
    t.add_column("Stake", justify="right")
    t.add_column("Status")
    t.add_column("P&L", justify="right")

    STATUS_COLOR = {"open": "cyan", "won": "green", "lost": "red", "push": "yellow", "void": "dim"}
    for b in rows:
        pnl_str = ""
        if b["status"] != "open":
            pnl_val = (b["payout"] or 0) - b["stake"]
            c = "green" if pnl_val >= 0 else "red"
            pnl_str = f"[{c}]{pnl_val:+.1f}[/{c}]"
        sc = STATUS_COLOR.get(b["status"], "white")
        t.add_row(
            str(b["id"]), b["game_date"], b["event_desc"] or "",
            b["selection"],
            f"{b['odds_taken']:.2f}", f"{b['stake']:.1f}",
            f"[{sc}]{b['status'].upper()}[/{sc}]", pnl_str,
        )
    console.print(t)


@app.command()
def snapshot(
    date: Optional[str] = typer.Option(None, help="Date YYYY-MM-DD (default: today UTC)"),
    commit: bool = typer.Option(False, "--commit", help="Git commit and push snapshot"),
):
    """Fetch Pinnacle closing lines and save to data/snapshots/. Run on Hetzner via cron."""
    from .sports.snapshot import fetch_and_save
    console.print("Fetching odds snapshot...")
    try:
        path, remaining = fetch_and_save(date=date, commit=commit)
        console.print(f"[green]✓[/green] Saved to {path}")
        console.print(f"[dim]API requests remaining: {remaining}[/dim]")
        if commit:
            console.print("[green]✓[/green] Committed and pushed.")
    except Exception as e:
        console.print(f"[red]Error: {e}[/red]")
        raise typer.Exit(1)


@app.command("auto-close")
def auto_close(
    date: Optional[str] = typer.Option(None, help="Game date YYYY-MM-DD (default: yesterday UTC)"),
):
    """Apply closing lines from snapshot to open bets for CLV tracking."""
    from datetime import timedelta
    from .sports.snapshot import load_snapshot, find_event_in_snapshot
    from .core.journal import close_bet

    if not date:
        date = (datetime.now(timezone.utc) - timedelta(days=1)).strftime("%Y-%m-%d")

    snapshot = load_snapshot(date)
    if not snapshot:
        console.print(f"[yellow]No snapshot found for {date}. Run `betlab snapshot` first.[/yellow]")
        raise typer.Exit(1)

    with get_conn() as conn:
        open_bets = [
            dict(r) for r in conn.execute(
                "SELECT * FROM bets WHERE status='open' AND game_date=? AND odds_close IS NULL",
                (date,),
            ).fetchall()
        ]

    if not open_bets:
        console.print(f"[yellow]No open bets without closing line for {date}.[/yellow]")
        return

    applied = 0
    for bet in open_bets:
        ev = find_event_in_snapshot(snapshot, bet["team_home"] or "", bet["team_away"] or "")
        if not ev:
            console.print(f"  [dim]#{bet['id']} {bet['selection'][:30]} — event not found in snapshot[/dim]")
            continue

        market = bet["market"]
        closing_odds: float | None = None

        if market in ("total_over", "total_under") and "totals" in ev["markets"]:
            side = "Over" if "over" in market else "Under"
            outcomes = ev["markets"]["totals"]["outcomes"]
            o = next((x for x in outcomes if x["name"] == side), None)
            if o:
                closing_odds = o["price"]
        elif market in ("moneyline_home", "moneyline_away") and "h2h" in ev["markets"]:
            team = ev["home_team"] if "home" in market else ev["away_team"]
            outcomes = ev["markets"]["h2h"]["outcomes"]
            o = next((x for x in outcomes if x["name"] == team), None)
            if o:
                closing_odds = o["price"]
        elif "spread" in market and "spreads" in ev["markets"]:
            team = ev["home_team"] if "home" in market else ev["away_team"]
            outcomes = ev["markets"]["spreads"]["outcomes"]
            o = next((x for x in outcomes if x["name"] == team), None)
            if o:
                closing_odds = o["price"]

        if closing_odds:
            from .core.journal import compute_clv
            clv = close_bet(bet["id"], closing_odds)
            color = "green" if (clv or 0) >= 0 else "red"
            console.print(
                f"  [bold]#{bet['id']}[/bold] {bet['selection'][:30]} "
                f"close {closing_odds:.2f}  CLV [{color}]{clv:+.2f}%[/{color}]"
            )
            applied += 1
        else:
            console.print(f"  [dim]#{bet['id']} {bet['selection'][:30]} — market not in snapshot[/dim]")

    console.print(f"\n[green]✓[/green] Applied closing lines to {applied}/{len(open_bets)} bets.")


@app.command()
def learn(
    since: Optional[str] = typer.Option(None, help="From date YYYY-MM-DD"),
):
    """Generate lessons from settled bets and append to ~/.betlab/LEARNINGS.md."""
    from .core.learn import run_learn, LEARNINGS_PATH
    count = run_learn(since=since)
    if count == 0:
        console.print("[yellow]No new settled bets to learn from.[/yellow]")
    else:
        console.print(f"[green]✓[/green] {count} lesson(s) written to {LEARNINGS_PATH}")


@app.command()
def odds(
    props: bool = typer.Option(False, "--props", help="Also fetch player props (costs extra API calls)"),
):
    """Show today's NBA sharp lines (Pinnacle/DraftKings/FanDuel)."""
    from datetime import timezone
    from .sports.odds import get_event_props, get_nba_odds, extract_sharp_line

    console.print("Fetching sharp lines...")
    try:
        events, remaining = get_nba_odds()
    except Exception as e:
        console.print(f"[red]Odds API error: {e}[/red]")
        raise typer.Exit(1)

    if not events:
        console.print("[yellow]No NBA games found.[/yellow]")
        console.print(f"[dim]API requests remaining: {remaining}[/dim]")
        return

    for ev in events:
        home = ev["home_team"]
        away = ev["away_team"]
        tip = ev["commence_time"][:16].replace("T", " ") + " UTC"
        console.print(f"\n[bold]{away} @ {home}[/bold]  [{tip}]")

        # Totals
        total = extract_sharp_line(ev, "totals")
        if total:
            over = next((o for o in total["outcomes"] if o["name"] == "Over"), None)
            under = next((o for o in total["outcomes"] if o["name"] == "Under"), None)
            pt = over.get("point", "?") if over else "?"
            console.print(
                f"  Total {pt}  "
                f"O [cyan]{over['price']:.2f}[/cyan]  "
                f"U [cyan]{under['price']:.2f}[/cyan]"
                f"  [dim]({total['book']})[/dim]"
            )

        # Spread
        spread = extract_sharp_line(ev, "spreads")
        if spread:
            for o in spread["outcomes"]:
                console.print(
                    f"  {o['name']} {o.get('point', ''):+.1f}  [cyan]{o['price']:.2f}[/cyan]"
                )
            console.print(f"  [dim]({spread['book']})[/dim]")

        # Moneyline
        h2h = extract_sharp_line(ev, "h2h")
        if h2h:
            for o in h2h["outcomes"]:
                console.print(f"  ML {o['name']}  [cyan]{o['price']:.2f}[/cyan]")
            console.print(f"  [dim]({h2h['book']})[/dim]")

        # Props
        if props:
            console.print("  [dim]Fetching props...[/dim]")
            try:
                prop_data, remaining = get_event_props(ev["id"])
                books = prop_data.get("bookmakers", [])
                seen: set[str] = set()
                for book in books:
                    for market in book.get("markets", []):
                        for outcome in market.get("outcomes", []):
                            key = f"{market['key']}|{outcome['name']}|{outcome.get('point','')}"
                            if key in seen:
                                continue
                            seen.add(key)
                            pt_str = f" {outcome['point']}" if "point" in outcome else ""
                            console.print(
                                f"  {market['key'].replace('player_', '').upper()} "
                                f"{outcome['name']}{pt_str}  [cyan]{outcome['price']:.2f}[/cyan]"
                                f"  [dim]({book['key']})[/dim]"
                            )
            except Exception as e:
                console.print(f"  [yellow]Props error: {e}[/yellow]")

    console.print(f"\n[dim]API requests remaining: {remaining}[/dim]")


if __name__ == "__main__":
    app()
