#!/usr/bin/env python3
"""Fast read-only parser for V2 bot journal.

Use this when you need the latest trade context without manually digging
through the huge JSONL rows.
"""
from __future__ import annotations

import argparse
import json
import time
from pathlib import Path
from typing import Any


def load_rows(path: Path, since_ms: int, coin: str | None = None) -> list[dict[str, Any]]:
    rows: list[dict[str, Any]] = []
    if not path.exists():
        raise SystemExit(f"journal not found: {path}")
    for line in path.read_text().splitlines():
        try:
            row = json.loads(line)
        except json.JSONDecodeError:
            continue
        if int(row.get("ts") or 0) < since_ms:
            continue
        if coin and str(row.get("coin") or "").upper() != coin.upper():
            continue
        rows.append(row)
    return rows


def fnum(value: Any, default: float = 0.0) -> float:
    try:
        return float(value)
    except (TypeError, ValueError):
        return default


def pct(part: float, whole: float) -> float:
    return part / whole * 100 if whole else 0.0


def fmt(value: Any, digits: int = 6) -> str:
    num = fnum(value)
    if abs(num) >= 100:
        return f"{num:.2f}"
    if abs(num) >= 1:
        return f"{num:.{digits}f}".rstrip("0").rstrip(".")
    return f"{num:.8f}".rstrip("0").rstrip(".")


def one_line(text: Any, limit: int = 220) -> str:
    raw = " ".join(str(text or "").split())
    if limit <= 0:
        return raw
    if len(raw) <= limit:
        return raw
    return raw[: limit - 3] + "..."


def event_counts(rows: list[dict[str, Any]]) -> dict[str, int]:
    counts: dict[str, int] = {}
    for row in rows:
        event = str(row.get("event") or "?")
        counts[event] = counts.get(event, 0) + 1
    return counts


def latest(rows: list[dict[str, Any]], event: str) -> dict[str, Any] | None:
    for row in reversed(rows):
        if row.get("event") == event:
            return row
    return None


def print_latest_submit(row: dict[str, Any] | None) -> None:
    print("\n=== LATEST SUBMIT ===")
    if not row:
        print("none")
        return

    side = str(row.get("side") or "").lower()
    entry = fnum(row.get("entry"))
    stop = fnum(row.get("stop"))
    target = fnum(row.get("target"))
    stop_dist = abs(entry - stop)
    reward_dist = abs(target - entry)
    stop_pct = pct(stop_dist, entry)
    target_pct = pct(reward_dist, entry)
    rr = fnum(row.get("rr"))
    metrics = row.get("exit_plan_metrics") if isinstance(row.get("exit_plan_metrics"), dict) else {}
    noise = metrics.get("noise_state") if isinstance(metrics.get("noise_state"), dict) else {}
    dynamic_floor = fnum(noise.get("dynamic_stop_floor"))
    atr14 = fnum(noise.get("atr14"))
    rr_after_noise = fnum(metrics.get("rr_after_noise_stop"))

    print(f"{row.get('time')} | {row.get('coin')} {side.upper()} | {row.get('setup_type')} | conf={row.get('confidence')}")
    print(
        f"entry={fmt(entry)} stop={fmt(stop)} target={fmt(target)} "
        f"rr={rr:.2f} stop={stop_pct:.2f}% target={target_pct:.2f}%"
    )
    print(
        f"qty={row.get('qty')} margin=${fnum(row.get('margin_usd')):.2f} "
        f"lev=x{row.get('leverage')} reason={one_line(row.get('reason'), 180)}"
    )

    if metrics:
        print(
            "noise: "
            f"stop_dist={fmt(stop_dist)} floor={fmt(dynamic_floor)} "
            f"stop/floor={(stop_dist / dynamic_floor if dynamic_floor else 0):.2f} "
            f"ATR={fmt(atr14)} stop/ATR={(stop_dist / atr14 if atr14 else 0):.2f} "
            f"1mP80={fmt(noise.get('range_p80_1m'))} 5mP80={fmt(noise.get('range_p80_5m'))} "
            f"rr_after_noise={rr_after_noise:.2f}"
        )
        print(f"latest_1m={noise.get('last_1m')}")
        print(f"latest_5m={noise.get('last_5m')}")
        verdicts = []
        if dynamic_floor and stop_dist < dynamic_floor:
            verdicts.append("BAD: stop below dynamic noise floor")
        elif dynamic_floor and stop_dist < dynamic_floor * 1.25:
            verdicts.append("BORDERLINE: stop only slightly above dynamic noise floor")
        if atr14 and stop_dist < atr14 * 1.2:
            verdicts.append("WARN: stop below prompt ATR*1.2 rule")
        if rr_after_noise and rr_after_noise < 1.5:
            verdicts.append("BAD: rr_after_noise below 1.5")
        last_5m = noise.get("last_5m") if isinstance(noise.get("last_5m"), dict) else {}
        direction = str(last_5m.get("direction") or "")
        if side == "short" and direction == "up":
            verdicts.append("WARN: latest 5m candle direction is against short")
        if side == "long" and direction == "down":
            verdicts.append("WARN: latest 5m candle direction is against long")
        print("verdict:", "; ".join(verdicts) if verdicts else "OK")

    playbook = row.get("playbook_match") if isinstance(row.get("playbook_match"), dict) else {}
    if playbook:
        print(f"playbook={playbook.get('playbook')} trigger={playbook.get('trigger_present')} level={one_line(playbook.get('trigger_level'), 180)}")
        print(f"response={one_line(playbook.get('price_response'), 180)}")

    levels = row.get("level_reasoning") if isinstance(row.get("level_reasoning"), dict) else {}
    if levels:
        print("level_reasoning:")
        for key in ("entry", "stop", "target"):
            print(f"  {key}: {one_line(levels.get(key), 220)}")

    exit_plan = row.get("exit_plan") if isinstance(row.get("exit_plan"), dict) else {}
    if exit_plan:
        print("exit_plan:")
        for key in ("stop_invalidation_level", "stop_noise_check", "target_level", "target_reason", "rr_after_noise_stop", "if_wrong_fast"):
            print(f"  {key}: {one_line(exit_plan.get(key), 220)}")


def print_json_block(title: str, value: Any) -> None:
    print(f"{title}:")
    if value in (None, "", [], {}):
        print("  null")
        return
    rendered = json.dumps(value, indent=2, ensure_ascii=False)
    for line in rendered.splitlines():
        print(f"  {line}")


def print_full_llm_decisions(rows: list[dict[str, Any]], limit: int) -> None:
    selected = [row for row in rows if row.get("event") == "llm_decision"][-limit:]
    print(f"\n=== FULL LLM DECISIONS ({len(selected)}) ===")
    for idx, row in enumerate(selected, 1):
        print(f"\n--- LLM #{idx} | {row.get('time')} | {row.get('coin') or '-'} {row.get('side') or '-'} ---")
        print(
            f"action={row.get('action')} setup={row.get('setup_type')} "
            f"conf={row.get('confidence')} entry={fmt(row.get('entry'))} "
            f"stop={fmt(row.get('stop'))} target={fmt(row.get('target'))}"
        )
        print(f"reason: {row.get('reason') or ''}")
        print(f"risk_assessment: {row.get('risk_assessment') or ''}")
        print(f"regime_note: {row.get('regime_note') or ''}")
        print_json_block("analysis_steps", row.get("analysis_steps"))
        print_json_block("playbook_match", row.get("playbook_match"))
        print_json_block("trade_plan", row.get("trade_plan"))
        print_json_block("level_reasoning", row.get("level_reasoning"))
        print_json_block("exit_plan", row.get("exit_plan"))
        print_json_block("best_candidate", row.get("best_candidate"))
        print_json_block("evidence", row.get("evidence"))
        raw = row.get("raw_response") or ""
        print("raw_response:")
        print(raw if raw else "null")


def print_full_rejects(rows: list[dict[str, Any]], limit: int) -> None:
    selected = [row for row in rows if row.get("event") == "risk_reject"][-limit:]
    print(f"\n=== FULL RISK REJECTS ({len(selected)}) ===")
    for idx, row in enumerate(selected, 1):
        print(f"\n--- REJECT #{idx} | {row.get('time')} | {row.get('coin') or '-'} {row.get('side') or '-'} ---")
        print(
            f"setup={row.get('setup_type')} conf={row.get('confidence')} "
            f"entry={fmt(row.get('entry'))} stop={fmt(row.get('stop'))} target={fmt(row.get('target'))}"
        )
        print(f"reason: {row.get('reason') or ''}")
        for key in (
            "gate",
            "gate_reasons",
            "gate_metrics",
            "analysis_steps",
            "playbook_match",
            "trade_plan",
            "level_reasoning",
            "exit_plan",
        ):
            if key in row:
                print_json_block(key, row.get(key))


def print_recent(rows: list[dict[str, Any]], event: str, limit: int, *, compact: bool = False) -> None:
    selected = [row for row in rows if row.get("event") == event][-limit:]
    print(f"\n=== RECENT {event.upper()} ({len(selected)}) ===")
    reason_limit = 180 if compact else 0
    for row in selected:
        print(
            f"{row.get('time')} | {row.get('coin') or '-'} {row.get('side') or '-'} "
            f"| {one_line(row.get('reason'), reason_limit)}"
        )


def main() -> int:
    parser = argparse.ArgumentParser(description="Quick V2 journal parser.")
    parser.add_argument("--journal", default="data/journal.jsonl")
    parser.add_argument("--hours", type=float, default=6.0)
    parser.add_argument("--coin")
    parser.add_argument("--last", type=int, default=5)
    parser.add_argument("--json", action="store_true", help="Print latest submit as JSON.")
    parser.add_argument("--full", action="store_true", help="Print full LLM decisions and reject details.")
    parser.add_argument("--llm-only", action="store_true", help="Only print full LLM decisions.")
    parser.add_argument("--compact", action="store_true", help="Truncate recent one-line reasons.")
    args = parser.parse_args()

    since_ms = int((time.time() - args.hours * 3600) * 1000)
    rows = load_rows(Path(args.journal), since_ms, args.coin)
    counts = event_counts(rows)

    if args.json:
        print(json.dumps(latest(rows, "submit") or {}, indent=2, ensure_ascii=False))
        return 0

    print(f"journal={args.journal} window={args.hours:g}h coin={args.coin or 'ALL'} rows={len(rows)} counts={counts}")
    if args.llm_only:
        print_full_llm_decisions(rows, args.last)
        return 0

    print_latest_submit(latest(rows, "submit"))
    if args.full:
        print_full_rejects(rows, args.last)
        print_full_llm_decisions(rows, args.last)
    else:
        print_recent(rows, "risk_reject", args.last, compact=args.compact)
        print_recent(rows, "llm_decision", args.last, compact=args.compact)
    return 0


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