#!/usr/bin/env python3
"""Render the 2026-07-20 UTC bot1 daily review from live read-only evidence."""

from __future__ import annotations

import base64
import json
import re
import subprocess
import urllib.parse
import urllib.request
from datetime import datetime, timezone
from pathlib import Path

import matplotlib.dates as mdates
import matplotlib.pyplot as plt
import pandas as pd
from matplotlib.lines import Line2D
from matplotlib.patches import Rectangle


HOST = "ubuntu@43.164.75.52"
START = "2026-07-20T00:00:00Z"
END = "2026-07-21T00:00:00Z"
PAIRS = ["ZEC/USDT:USDT", "BTC/USDT:USDT", "XRP/USDT:USDT", "DOGE/USDT:USDT", "VANRY/USDT:USDT", "ADA/USDT:USDT", "EDGE/USDT:USDT"]
OUT = Path(__file__).resolve().parent

COLORS = {
    "up": "#00A344", "down": "#D50000", "signal": "#2563EB",
    "block": "#64748B", "order": "#D97706", "fill": "#111827",
    "stop": "#DC2626", "exit": "#0F766E", "profit": "#15803D",
    "loss": "#B91C1C", "indicator": "#087F8C", "secondary": "#5D6B78",
}

REMOTE = r'''
import base64,json,re,sqlite3,sys,urllib.parse,urllib.request
from datetime import datetime,timezone
start=datetime.fromisoformat(sys.argv[1].replace("Z","+00:00")); end=datetime.fromisoformat(sys.argv[2].replace("Z","+00:00")); pairs=sys.argv[3:]
cfg=json.load(open("/data/freqtrade/user_data/config.json")); token=base64.b64encode((cfg["api_server"]["username"]+":"+cfg["api_server"]["password"]).encode()).decode()
def candles(pair):
 q=urllib.parse.urlencode({"pair":pair,"timeframe":"5m","limit":"1000"}); req=urllib.request.Request("http://127.0.0.1:8080/api/v1/pair_candles?"+q,headers={"Authorization":"Basic "+token,"Accept":"application/json"})
 with urllib.request.urlopen(req,timeout=15) as r: p=json.load(r)
 return [dict(zip(p["columns"],row)) for row in p["data"]]
con=sqlite3.connect("file:/data/freqtrade/user_data/tradesv3-v5.sqlite?mode=ro",uri=True); con.row_factory=sqlite3.Row
s=start.astimezone(timezone.utc).replace(tzinfo=None).isoformat(sep=" ",timespec="microseconds"); e=end.astimezone(timezone.utc).replace(tzinfo=None).isoformat(sep=" ",timespec="microseconds")
trades=[dict(r) for r in con.execute("""select id,pair,is_short,open_date,close_date,open_rate,close_rate,stake_amount,leverage,stop_loss,initial_stop_loss,close_profit,close_profit_abs,exit_reason from trades where (open_date>=? and open_date<?) or (open_date<? and (close_date is null or close_date>=?)) order by open_date""",(s,e,s,s))]
orders={str(t["id"]):[dict(r) for r in con.execute("""select id,ft_order_side,order_type,status,price,average,order_date,order_filled_date,order_update_date,stop_price from orders where ft_trade_id=? order by order_date,id""",(t["id"],))] for t in trades}
pat=re.compile(r"^(?P<time>2026-07-20 \d\d:\d\d:\d\d).*entry_blocked reason=(?P<reason>\S+) pair=(?P<pair>\S+) side=(?P<side>\S+) occupied=(?P<occupied>.*)$")
blocks=[]
for line in open("/data/freqtrade/user_data/logs/freqtrade.log",errors="replace"):
 m=pat.search(line)
 if m: blocks.append(m.groupdict())
print(json.dumps({"fetched_at":datetime.now(timezone.utc).isoformat(),"candles":{p:candles(p) for p in pairs},"trades":trades,"orders":orders,"blocks":blocks},separators=(",",":"),default=str))
'''


def fetch() -> dict:
    result = subprocess.run(
        ["ssh", HOST, "python3", "-", START, END, *PAIRS], input=REMOTE,
        text=True, capture_output=True, check=True, timeout=90,
    )
    return json.loads(result.stdout)


def stamp(value: str) -> pd.Timestamp:
    value = value.replace(" ", "T")
    return pd.Timestamp(value, tz="UTC") if pd.Timestamp(value).tzinfo is None else pd.Timestamp(value).tz_convert("UTC")


def frame(rows: list[dict]) -> pd.DataFrame:
    f = pd.DataFrame(rows)
    f["date"] = pd.to_datetime(f["date"], utc=True)
    f = f.set_index("date").sort_index()
    for col in ["open", "high", "low", "close", "volume", "donchian_high", "donchian_low", "ema_trend", "atr", "adx", "enter_short"]:
        f[col] = pd.to_numeric(f.get(col), errors="coerce")
    return f


def candles(ax: plt.Axes, f: pd.DataFrame, minutes: int) -> None:
    width = minutes / 1440 * 0.68
    floor = max((f.high.max() - f.low.min()) / 1200, 1e-12)
    for t, row in f.iterrows():
        x = mdates.date2num(t.to_pydatetime()); color = COLORS["up"] if row.close >= row.open else COLORS["down"]
        ax.vlines(x, row.low, row.high, color=color, linewidth=.55, zorder=2)
        height = max(abs(row.close-row.open), floor); bottom = min(row.open,row.close) if abs(row.close-row.open)>=floor else (row.open+row.close-height)/2
        ax.add_patch(Rectangle((x-width/2,bottom),width,height,facecolor=color,edgecolor=color,linewidth=0,zorder=2))


def style(ax: plt.Axes) -> None:
    ax.set_facecolor("white"); ax.grid(True, color="#64748B", alpha=.10, linewidth=.7)
    for s in ax.spines.values(): s.set_color("#CBD5E1")
    ax.tick_params(labelsize=8, colors="#475569"); ax.xaxis.set_major_formatter(mdates.DateFormatter("%m-%d\n%H:%M", tz=timezone.utc))


def signal_before(f: pd.DataFrame, when: pd.Timestamp) -> tuple[pd.Timestamp, pd.Series] | None:
    eligible = f[(f.index + pd.Timedelta(minutes=5) <= when) & (f.index >= when-pd.Timedelta(minutes=15))]
    eligible = eligible[eligible.enter_short.fillna(0) == 1]
    return None if eligible.empty else (eligible.index[-1], eligible.iloc[-1])


def plot_indicators(ax: plt.Axes, f: pd.DataFrame) -> None:
    ax.plot(f.index, f.donchian_low, color=COLORS["indicator"], linewidth=.9, alpha=.85, label="Donchian low")
    ax.plot(f.index, f.ema_trend, color=COLORS["secondary"], linewidth=.8, alpha=.7, label="EMA200")


def render_execution(ev: dict, frames: dict[str, pd.DataFrame]) -> Path:
    fig, axes = plt.subplots(2,2,figsize=(20,12),dpi=160,facecolor="white")
    starts = {"ZEC/USDT:USDT":"2026-07-19T11:30Z","BTC/USDT:USDT":"2026-07-19T11:45Z","XRP/USDT:USDT":"2026-07-20T13:15Z","DOGE/USDT:USDT":"2026-07-20T18:35Z"}
    ends = {"ZEC/USDT:USDT":"2026-07-20T13:30Z","BTC/USDT:USDT":END,"XRP/USDT:USDT":"2026-07-20T21:00Z","DOGE/USDT:USDT":END}
    for ax,t in zip(axes.flat,ev["trades"]):
        pair=t["pair"]; local=frames[pair].loc[stamp(starts[pair]):stamp(ends[pair])].copy(); candles(ax,local,5); plot_indicators(ax,local)
        open_t=stamp(t["open_date"]); sig=signal_before(frames[pair],open_t)
        if sig: ax.scatter(sig[0],sig[1].close,marker="v",s=90,color=COLORS["signal"],edgecolor="white",linewidth=.7,zorder=7)
        entry=next((o for o in ev["orders"][str(t["id"])] if o["order_type"]=="limit" and o["ft_order_side"]=="sell"),None)
        if entry: ax.axvline(stamp(entry["order_date"]),color=COLORS["order"],linestyle="--",linewidth=1.1); fill_t=stamp(entry["order_filled_date"] or t["open_date"])
        else: fill_t=open_t
        ax.scatter(fill_t,t["open_rate"],s=44,color=COLORS["fill"],zorder=8)
        stops=[o for o in ev["orders"][str(t["id"])] if o["ft_order_side"]=="stoploss" and o.get("stop_price")]
        if stops:
            xs=[stamp(o["order_date"]) for o in stops]; ys=[o["stop_price"] for o in stops]
            ax.step(xs,ys,where="post",color=COLORS["stop"],linewidth=1.15,alpha=.9)
        if t["close_date"]:
            color=COLORS["profit"] if (t["close_profit"] or 0)>0 else COLORS["loss"]
            ax.scatter(stamp(t["close_date"]),t["close_rate"],marker="X",s=75,color=COLORS["stop"],edgecolor="white",linewidth=.7,zorder=9)
            result=f"{t['close_profit_abs']:+.2f} USDT / {t['close_profit']*100:+.2f}%"
        else: color=COLORS["secondary"]; result="OPEN at day end"
        ax.text(.015,.965,result,transform=ax.transAxes,va="top",fontsize=9,color=color,bbox={"facecolor":"white","edgecolor":color,"alpha":.9,"boxstyle":"round,pad=.3"})
        ax.axvline(stamp(END),color="#94A3B8",linestyle=":",linewidth=.9)
        ax.set_title(f"{pair} · SHORT · trade #{t['id']}",loc="left",fontsize=11,fontweight="bold"); style(ax)
    legend=[Line2D([0],[0],marker="v",color="none",markerfacecolor=COLORS["signal"],label="Strategy short signal",markersize=8),Line2D([0],[0],color=COLORS["order"],linestyle="--",label="Submitted order"),Line2D([0],[0],marker="o",color="none",markerfacecolor=COLORS["fill"],label="Fill",markersize=7),Line2D([0],[0],color=COLORS["stop"],label="Active stop / stop exit"),Line2D([0],[0],color=COLORS["indicator"],label="Donchian low"),Line2D([0],[0],color=COLORS["secondary"],label="EMA200")]
    fig.legend(handles=legend,loc="lower center",ncol=6,frameon=False,fontsize=9); fig.suptitle("Bot1 D48 · 2026-07-20 UTC · 5m execution reconstruction",fontsize=16,fontweight="bold"); fig.text(.5,.025,"Authentic Freqtrade analyzed candles and SQLite orders · carried-in positions include pre-window context",ha="center",fontsize=9,color="#64748B")
    fig.tight_layout(rect=[0,.06,1,.95]); path=OUT/"bot1_2026-07-20_execution_5m.png"; fig.savefig(path,bbox_inches="tight",facecolor="white"); plt.close(fig); return path


def resample_15m(f: pd.DataFrame) -> pd.DataFrame:
    return f.resample("15min",label="left",closed="left").agg({"open":"first","high":"max","low":"min","close":"last","volume":"sum","donchian_low":"last","ema_trend":"last"}).dropna(subset=["open","close"])


def render_blocks(ev: dict, frames: dict[str,pd.DataFrame]) -> Path:
    fig,axes=plt.subplots(3,1,figsize=(20,14),dpi=160,facecolor="white")
    for ax,b in zip(axes,ev["blocks"]):
        pair=b["pair"]; when=stamp(b["time"]); raw=frames[pair]; local5=raw.loc[when-pd.Timedelta(hours=1):when+pd.Timedelta(hours=6)]; local=resample_15m(local5)
        candles(ax,local,15); plot_indicators(ax,local); sig=signal_before(raw,when)
        if sig: ax.scatter(sig[0],sig[1].close,marker="v",s=90,color=COLORS["signal"],edgecolor="white",linewidth=.7,zorder=7); anchor=float(sig[1].close)
        else: anchor=float(local5.iloc[0].close)
        ax.scatter(when,anchor,marker="x",s=100,color=COLORS["block"],linewidth=2.2,zorder=8)
        future=local5.loc[when:when+pd.Timedelta(hours=6)]; low=float(future.low.min()); high=float(future.high.max()); mfe=(anchor-low)/anchor*200; mae=(anchor-high)/anchor*200
        ax.text(.015,.965,f"RISK_GATE_BLOCK · same_side_limit\n6h hindsight: MFE {mfe:+.2f}% · MAE {mae:+.2f}%",transform=ax.transAxes,va="top",fontsize=9,color="#334155",bbox={"facecolor":"white","edgecolor":"#94A3B8","linestyle":":","alpha":.92,"boxstyle":"round,pad=.35"})
        ax.set_title(f"{pair} · exact 5m signal/block over resampled 15m structure",loc="left",fontsize=11,fontweight="bold"); style(ax)
    legend=[Line2D([0],[0],marker="v",color="none",markerfacecolor=COLORS["signal"],label="Authentic 5m strategy signal",markersize=8),Line2D([0],[0],marker="x",color=COLORS["block"],label="Risk gate block",markersize=8,linestyle="none"),Line2D([0],[0],color=COLORS["indicator"],label="Donchian low (15m last)"),Line2D([0],[0],color=COLORS["secondary"],label="EMA200 (15m last)")]
    fig.legend(handles=legend,loc="lower center",ncol=4,frameon=False,fontsize=9); fig.suptitle("Bot1 D48 · 2026-07-20 UTC · blocked short signals · 15m structure",fontsize=16,fontweight="bold"); fig.text(.5,.025,"15m candles are explicitly resampled from authentic 5m analyzed candles; markers retain exact 5m timestamps. Outcome boxes are hindsight only.",ha="center",fontsize=9,color="#64748B")
    fig.tight_layout(rect=[0,.06,1,.95]); path=OUT/"bot1_2026-07-20_blocks_15m.png"; fig.savefig(path,bbox_inches="tight",facecolor="white"); plt.close(fig); return path


def main() -> int:
    ev=fetch(); frames={p:frame(rows) for p,rows in ev["candles"].items()}; paths=[render_execution(ev,frames),render_blocks(ev,frames)]
    manifest={"review_id":"2026-07-20-bot1-d48-daily","fetched_at":ev["fetched_at"],"window":{"start":START,"end":END},"candle_source":"Freqtrade /pair_candles authentic analyzed 5m","charts":[p.name for p in paths],"visual_review":None}
    (OUT/"manifest.json").write_text(json.dumps(manifest,ensure_ascii=False,indent=2)+"\n",encoding="utf-8")
    print("\n".join(str(p) for p in paths))
    return 0


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