"""
sym_day_14d_report.py -- [2026-09-29] Complete one-day report for a symbol plus its
14-day reference: the day section from day_report.py (premarket-to-close chart with
VWAP and the bot's trades, price vs open/VWAP, volume by hour, scanner ranks, entry
checks, news, trades), the 14 prior sessions' daily high/low table and chart with the
14-day support/resistance bands, the reference values the v2 rules used, and a v2
timeline for the whole session rebuilt from Alpaca tick data (every 5 minutes, what
v2 saw and which rule failed -- also for the times the bot wasn't watching).

    python3 sym_day_14d_report.py AGEN 2026-09-29  -> reports/<SYM>_<date>_complete_report.pdf
"""
import json
import subprocess
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
from zoneinfo import ZoneInfo

import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt

HERE = Path(__file__).resolve().parent
TRADE = Path("/var/www/screener/trade")
sys.path.insert(0, str(HERE))
sys.path.insert(0, str(TRADE))
import day_report as R
import entry_v2
import reference
from alpaca_client import get_client
from simulate import SymbolEventReader, cache_symbol
from stream import SymbolBuffer

ET = ZoneInfo("America/New_York")
UTC = timezone.utc
MD2PDF = "/tmp/claude-0/-var-www-screener-trade/5844eef7-c648-46de-a03c-fe0b1434b640/scratchpad/md2pdf.py"


def watch_windows(sym, day):
    """[(start_hhmm, end_hhmm)] the symbol was on the live watchlist, from the scan logs."""
    log = TRADE / "logs" / f"trade_bot_{day}.log"
    scans = []
    for line in open(log) if log.exists() else []:
        if "[SCAN]" in line and "candidates selected" in line:
            scans.append((line[11:16], f"'{sym}'" in line))
    out, start = [], None
    for t, on in scans:
        if on and start is None:
            start = t
        if not on and start is not None:
            out.append((start, t))
            start = None
    if start is not None:
        out.append((start, "16:00"))
    return out


def fourteen_day(sym, day, ref):
    d = datetime.fromisoformat(day).replace(tzinfo=ET)
    daily = get_client().get_daily_bars_bulk([sym], d - timedelta(days=30), d + timedelta(hours=20)).get(sym, [])
    rows = [b for b in daily if b["t"].astimezone(ET).date().isoformat() in ref["sessions"]]
    today = [b for b in daily if b["t"].astimezone(ET).date().isoformat() == day]
    return rows, (today[0] if today else None)


def v2_timeline(sym, day, ref, windows, trades, day_open):
    start = datetime.fromisoformat(day).replace(hour=9, minute=30, tzinfo=ET).astimezone(UTC)
    end = datetime.fromisoformat(day).replace(hour=16, minute=0, tzinfo=ET).astimezone(UTC)
    cache = TRADE / "data" / "simulations" / "cache" / day / f"{sym}.jsonl"
    if not cache.exists():
        cache.parent.mkdir(parents=True, exist_ok=True)
        cache_symbol(get_client(), sym, start, end, cache)
    buf, rd = SymbolBuffer(), SymbolEventReader(cache)
    cfg = json.load(open(TRADE / "config.json")).get("entry_v2", {})
    buys = {datetime.fromisoformat(t["entry_time"]).astimezone(ET).strftime("%H:%M") for t in trades}
    sells = {datetime.fromisoformat(t["exit_time"]).astimezone(ET).strftime("%H:%M") for t in trades if t.get("exit_time")}
    rows, t = [], start + timedelta(minutes=15)
    rd.drain_up_to(t, buf)
    while t <= end - timedelta(minutes=5):
        rd.drain_up_to(t, buf)
        hm = t.astimezone(ET).strftime("%H:%M")
        near = [x for x in buys | sells if abs((datetime.strptime(x, "%H:%M") - datetime.strptime(hm, "%H:%M")).total_seconds()) < 150]
        if t.minute % 5 == 0 or near:
            q = buf.latest_quote
            d = entry_v2.evaluate(sym, buf.get_bars(), ref, buf.get_flow(15), {}, as_of=t, cfg=cfg,
                                  quote={"bid": q[0], "ask": q[1]} if q else None)
            m = d.metrics
            watched = any(a <= hm < b for a, b in windows)
            g = lambda k, f="{:.2f}": f.format(m[k]) if isinstance(m.get(k), (int, float)) else "—"
            mark = " **BUY**" if hm in buys else " **SELL**" if hm in sells else ""
            ra = d.reasons_against or []
            if d.state == "WAIT" and ra and not ra[0].startswith("confirming"):
                why = f"WAIT: {ra[0]}"
            elif d.state != "REJECT":
                why = "all rules pass"
            else:
                why = "; ".join(r.split(":")[0] + ":" + r.split(":", 1)[1][:28] if ":" in r else r for r in d.reasons_against[:2])
            rows.append(f"| {hm}{mark} | {'yes' if watched else 'no'} | {g('price')} | {g('vwap_pct', '{:+.1f}')} | "
                        f"{g('rvol_sofar', '{:.1f}')} | {g('vol15_x', '{:.1f}')} | "
                        f"{(m.get('buy_share15') or 0) * 100:.0f}% | {g('r2_15')} | {g('eff15')} | "
                        f"{(m.get('price', 0) / day_open - 1) * 100:+.1f}% | {why} |")
        t += timedelta(minutes=1)
    return rows


def main(sym, day):
    ref = reference.load(day, sym)
    rows14, today = fourteen_day(sym, day, ref)
    trades = R.trades_for(sym, day)
    win = watch_windows(sym, day)
    sr = ref["sr14"]

    # 14-day chart: daily high-low bars + closes, S/R bands, today's range
    fig, ax = plt.subplots(figsize=(10.5, 4))
    xs = list(range(len(rows14)))
    for i, b in enumerate(rows14):
        ax.plot([i, i], [b["l"], b["h"]], color="#1f3a5f", lw=3)
        ax.plot([i - .25, i], [b["o"], b["o"]], color="#1f3a5f", lw=1.2)
        ax.plot([i, i + .25], [b["c"], b["c"]], color="#1f3a5f", lw=1.2)
    if today:
        n = len(rows14)
        ax.plot([n, n], [today["l"], today["h"]], color="#c0392b", lw=3)
        ax.plot([n - .25, n], [today["o"], today["o"]], color="#c0392b", lw=1.2)
        ax.plot([n, n + .25], [today["c"], today["c"]], color="#c0392b", lw=1.2)
    for lv in sr["levels"]:
        ax.axhspan(lv["lo"], lv["hi"] + 0.005, color="#f39c12", alpha=.25)
        ax.text(len(rows14) + .5, lv["mid"], f"{lv['lo']:.2f}–{lv['hi']:.2f} ({lv['touches']}×)", fontsize=6.5, va="center")
    ax.axhline(sr["high"], color="#27ae60", ls="--", lw=.8)
    ax.axhline(sr["low"], color="#c0392b", ls="--", lw=.8)
    labels = [b["t"].astimezone(ET).strftime("%m-%d") for b in rows14] + ([day[5:]] if today else [])
    ax.set_xticks(range(len(labels)))
    ax.set_xticklabels(labels, fontsize=7, rotation=45)
    ax.set_xlim(-.7, len(labels) + 2.2)
    ax.grid(alpha=.3)
    ax.set_title(f"{sym} — 14 sessions before {day} (blue) and {day} (red); orange = 14-day support/resistance bands; "
                 f"green/red dashed = 14-day high/low", fontsize=8.5)
    plt.tight_layout()
    png14 = HERE / f"{sym}_{day}_14d.png"
    plt.savefig(png14, dpi=120)
    plt.close()

    hi_b = max(rows14, key=lambda b: b["h"])
    lo_b = min(rows14, key=lambda b: b["l"])
    L = [f"# {sym} — Complete Report, {day}", "",
         "Source: Alpaca SIP 1-minute bars, trades and quotes; Alpaca daily bars; Alpaca news (Benzinga); "
         "the live bot's scanner lists, decision log and trade log; the 14-day reference file the v2 entry rules used "
         f"(data/reference/{day}/{sym}.json).", ""]
    if today:
        L += ["## The day at a glance", "",
              "| Open | High | Low | Close | Open→close | vs prev close | Range | Volume | vs 14-day avg volume |",
              "|---|---|---|---|---|---|---|---|---|",
              f"| {today['o']:.2f} | {today['h']:.2f} | {today['l']:.2f} | {today['c']:.2f} | "
              f"{(today['c'] / today['o'] - 1) * 100:+.1f}% | {(today['c'] / ref['prev_close'] - 1) * 100:+.1f}% | "
              f"{(today['h'] / today['l'] - 1) * 100:.1f}% | {today['v'] / 1e6:.2f}M | {today['v'] / ref['adv14']:.1f}× |", ""]
    L += ["**On the bot's watchlist (ET):** " + (", ".join(f"{a}–{b}" for a, b in win) or "never"), ""]

    L += ["## 14-day highs and lows", "", f"![]({png14})", "",
          "| Session | Open | High | Low | Close | Change | Range | Volume |", "|---|---|---|---|---|---|---|---|"]
    prev_c = None
    for b in rows14:
        chg = f"{(b['c'] / prev_c - 1) * 100:+.1f}%" if prev_c else "—"
        L.append(f"| {b['t'].astimezone(ET).strftime('%a %m-%d')} | {b['o']:.2f} | {b['h']:.2f} | {b['l']:.2f} | {b['c']:.2f} | "
                 f"{chg} | {(b['h'] / b['l'] - 1) * 100:.1f}% | {b['v'] / 1e6:.2f}M |")
        prev_c = b["c"]
    L += ["", "| 14-day reference | Value |", "|---|---|",
          f"| 14-day high | **{hi_b['h']:.2f}** ({hi_b['t'].astimezone(ET).strftime('%m-%d')}) |",
          f"| 14-day low | **{lo_b['l']:.2f}** ({lo_b['t'].astimezone(ET).strftime('%m-%d')}) |",
          f"| Previous day high / low / close | {ref['prev_high']:.2f} / {ref['prev_low']:.2f} / {ref['prev_close']:.2f} |",
          f"| 5-day high / low | {ref['ref_5d_high']:.2f} / {ref['ref_5d_low']:.2f} |",
          f"| Daily ATR (14) | ${ref['atr14']:.2f} |",
          f"| Average daily range (14) | {ref['avg_range_pct14']:.1f}% |",
          f"| Average daily volume (14) | {ref['adv14'] / 1e6:.2f}M |",
          f"| Normal volume by 10:00 / 12:00 / close | {ref['cum_vol'][29] / 1e3:.0f}k / {ref['cum_vol'][149] / 1e3:.0f}k / {ref['cum_vol'][389] / 1e6:.2f}M |",
          "", "**14-day support / resistance bands** (daily highs and lows within 0.25 × ATR, at least 2 touches)", "",
          "| Band | Touches | Last touch | vs today's open | vs today's high |", "|---|---|---|---|---|"]
    for lv in reversed(sr["levels"]):
        L.append(f"| {lv['lo']:.2f} – {lv['hi']:.2f} | {lv['touches']} | {lv['last_touch_days_ago']} sessions ago | "
                 + (f"{(lv['mid'] / today['o'] - 1) * 100:+.1f}% | {(lv['mid'] / today['h'] - 1) * 100:+.1f}% |" if today else "— | — |"))
    L += ["", "---", ""]

    L.append(R.section(sym, day))
    L += ["", "---", "", "## What the v2 entry rules saw, all session", "",
          "Rebuilt from Alpaca's tick data with the live v2 settings, every 5 minutes from 9:45 plus the minutes of the "
          "bot's buys and sells — including the times the bot wasn't watching or the re-entry rule blocked it (the live "
          "log didn't record v2's numbers then). Small differences from the live values are expected (odd-lot prints), and a snapshot on the minute can differ from the live check a few seconds later (the live 12:29:11 buy passed every rule; this 12:29:00 snapshot still shows the smoothness below 0.7). The trade log (above) has the exact values at each buy.", "",
          "Columns: **Watched** = on the live watchlist; **VWAP** = price vs VWAP; **RVOL** = volume so far ÷ 14-day normal "
          "by this minute; **15m×** = last 15 minutes' volume ÷ normal; **Buy** = buy share of the last 15 minutes; "
          "**R²** smoothness and **Eff** straightness of the last 15 bars; **Open** = price vs today's open.", "",
          "| ET | Watched | Price | VWAP % | RVOL | 15m× | Buy | R² | Eff | Open | v2 result (first failing rules) |",
          "|---|---|---|---|---|---|---|---|---|---|---|"]
    L += v2_timeline(sym, day, ref, win, trades, today["o"] if today else ref["prev_close"])
    mdp = HERE / f"{sym}_{day}_complete.md"
    mdp.write_text("\n".join(L))
    pdf = HERE.parent / f"{sym}_{day}_complete_report.pdf"
    subprocess.run([sys.executable, MD2PDF, str(mdp), str(pdf), f"{sym} Complete Report {day}"], check=True)
    print("wrote", pdf)


if __name__ == "__main__":
    main(sys.argv[1], sys.argv[2])
