#!/opt/homebrew/bin/python3.11
"""Face Trace Heatmap + Timeline Visualization for Momentry.
Usage:
  python3 render_face_heatmap.py <uuid> [output.html] [--identity ID]
"""
import sys, psycopg2, argparse
from collections import defaultdict

parser = argparse.ArgumentParser()
parser.add_argument("uuid")
parser.add_argument("output", nargs="?", default=None)
parser.add_argument("--identity", "-i", type=int, default=None, help="Filter by identity_id")
args = parser.parse_args()

UUID = args.uuid
OUT = args.output or f"/tmp/face_report_{UUID[:8]}.html"
IDENTITY = args.identity

conn = psycopg2.connect("dbname=momentry user=accusys")
cur = conn.cursor()

cur.execute("SELECT duration, file_name, COALESCE(fps, 25.0) FROM dev.videos WHERE file_uuid=%s", (UUID,))
row = cur.fetchone()
if not row:
    print("UUID not found")
    sys.exit(1)
duration, video_name, fps = float(row[0] or 6785), row[1] or UUID, float(row[2] or 25.0)

# Get sample interval from face.json metadata (or default 3 = 8Hz)
sample_interval = 3
hz = fps / sample_interval

# Build identity filter
identity_filter = ""
identity_params = [UUID]
identity_label = ""
identity_info = None  # full identity record when filtered
top_identities = []   # top identities summary (all view)

if IDENTITY is not None:
    identity_filter = " AND identity_id = %s"
    identity_params.append(IDENTITY)
    cur.execute("SELECT id, name, identity_type, source, status FROM dev.identities WHERE id=%s", (IDENTITY,))
    id_row = cur.fetchone()
    if id_row:
        identity_info = {"id": id_row[0], "name": id_row[1], "type": id_row[2], "source": id_row[3], "status": id_row[4]}
        identity_label = f" (identity: {id_row[1]})"
    else:
        identity_label = f" (identity #{IDENTITY})"
    identity_params = [UUID, IDENTITY]

# Query trace spans
cur.execute(f"""
    SELECT trace_id, MIN(frame_number), MAX(frame_number),
           COALESCE(MIN(timestamp_secs), MIN(frame_number) / {fps}) as first_t,
           COALESCE(MAX(timestamp_secs), MAX(frame_number) / {fps}) as last_t,
           COUNT(*)
    FROM dev.face_detections
    WHERE file_uuid=%s AND trace_id IS NOT NULL{identity_filter}
    GROUP BY trace_id ORDER BY first_t
""", identity_params)
trace_spans = cur.fetchall()

# Query density per time bucket (5s)
cur.execute(f"""
    SELECT FLOOR(COALESCE(timestamp_secs, frame_number / {fps}) / 5)::int as bkt, COUNT(*) as cnt
    FROM dev.face_detections
    WHERE file_uuid=%s AND trace_id IS NOT NULL{identity_filter}
    GROUP BY bkt ORDER BY bkt
""", identity_params)
density = {b: c for b, c in cur.fetchall()}

# Count total detections
cur.execute(f"SELECT COUNT(*) FROM dev.face_detections WHERE file_uuid=%s{identity_filter}", identity_params)
total_detections = cur.fetchone()[0]

# Get top identities (for all view) and trace↔identity mapping
if IDENTITY is None:
    cur.execute("""
        SELECT fd.identity_id, i.name, COUNT(*) as faces, COUNT(DISTINCT fd.trace_id) as traces
        FROM dev.face_detections fd
        LEFT JOIN dev.identities i ON i.id = fd.identity_id
        WHERE fd.file_uuid=%s AND fd.identity_id IS NOT NULL
        GROUP BY fd.identity_id, i.name ORDER BY faces DESC LIMIT 10
    """, (UUID,))
    top_identities = cur.fetchall()

# Always get trace→identity mapping for tooltip enrichment
cur.execute("""
    SELECT DISTINCT fd.trace_id, i.name
    FROM dev.face_detections fd
    LEFT JOIN dev.identities i ON i.id = fd.identity_id
    WHERE fd.file_uuid=%s AND fd.identity_id IS NOT NULL
""", (UUID,))
trace_to_identity = {r[0]: r[1] for r in cur.fetchall()}

cur.close(); conn.close()

BUCKET = 5
num_buckets = int(duration / BUCKET) + 1
max_density = max(density.values()) if density else 1

def build_html():
    h = []
    h.append('<!DOCTYPE html><html><head><meta charset="utf-8"><title>Face Trace Report</title>')
    h.append('<style>')
    h.append('body{font-family:-apple-system,BlinkMacSystemFont,sans-serif;margin:20px;background:#0d1117;color:#c9d1d9}')
    h.append('h1,h2{color:#e94560}')
    h.append('.stats{display:flex;gap:12px;margin:8px 0;flex-wrap:wrap}')
    h.append('.stat{background:#161b22;padding:6px 14px;border-radius:6px}')
    h.append('.stat .num{font-size:20px;font-weight:bold;color:#e94560}')
    h.append('.stat .label{font-size:10px;color:#8b949e}')
    h.append('.viz{position:relative;background:#0d1117;border:1px solid #30363d;margin:8px 0;overflow:hidden}')
    h.append('.bar{display:block;position:absolute;height:3px;background:#e94560;opacity:0.7;border-radius:1px}')
    h.append('.bar:hover{height:8px;opacity:1}')
    h.append('</style></head><body>')
    sub = identity_label if identity_label else ""
    h.append('<h1>Face Trace Report — ' + video_name[:60] + sub + '</h1>')

    # Identity card (when filtering by identity)
    if identity_info:
        h.append('<div style="background:#161b22;border:1px solid #30363d;border-radius:8px;padding:16px;margin:12px 0;">')
        h.append('<h3 style="margin:0;color:#e94560">Identity Details</h3>')
        h.append(f'<table style="width:100%;margin-top:8px;color:#c9d1d9;border-collapse:collapse">')
        h.append(f'<tr><td style="padding:4px 12px 4px 0;color:#8b949e;width:80px">ID</td><td>{identity_info["id"]}</td></tr>')
        h.append(f'<tr><td style="padding:4px 12px 4px 0;color:#8b949e">Name</td><td style="font-weight:bold">{identity_info["name"]}</td></tr>')
        h.append(f'<tr><td style="padding:4px 12px 4px 0;color:#8b949e">Type</td><td>{identity_info["type"]}</td></tr>')
        h.append(f'<tr><td style="padding:4px 12px 4px 0;color:#8b949e">Source</td><td>{identity_info["source"]}</td></tr>')
        h.append(f'<tr><td style="padding:4px 12px 4px 0;color:#8b949e">Status</td><td>{identity_info["status"]}</td></tr>')
        h.append('</table></div>')

    # Top identities table (all view)
    if top_identities:
        h.append('<h2>Top Identities</h2>')
        h.append('<div style="overflow-x:auto">')
        h.append('<table style="width:100%;color:#c9d1d9;border-collapse:collapse;font-size:13px">')
        h.append('<tr style="background:#161b22;text-align:left"><th style="padding:6px 10px">Identity</th><th style="padding:6px 10px">Name</th><th style="padding:6px 10px;text-align:right">Faces</th><th style="padding:6px 10px;text-align:right">Traces</th></tr>')
        for iid, name, fc, tc in top_identities:
            short_name = (name or f"#{iid}")[:60]
            h.append(f'<tr style="border-bottom:1px solid #21262d"><td style="padding:4px 10px;color:#8b949e">{iid}</td><td style="padding:4px 10px">{short_name}</td><td style="padding:4px 10px;text-align:right">{fc:,}</td><td style="padding:4px 10px;text-align:right">{tc}</td></tr>')
        h.append('</table></div>')

    # Stats row
    h.append('<div class="stats">')
    h.append(f'<div class="stat"><div class="num">{len(trace_spans):,}</div><div class="label">traces</div></div>')
    h.append(f'<div class="stat"><div class="num">{total_detections:,}</div><div class="label">detections</div></div>')
    h.append(f'<div class="stat"><div class="num">{duration:.0f}s</div><div class="label">duration</div></div>')
    h.append(f'<div class="stat"><div class="num">{max_density}</div><div class="label">max per {BUCKET}s</div></div>')
    h.append(f'<div class="stat"><div class="num">{fps:.0f}fps</div><div class="label">video fps</div></div>')
    h.append(f'<div class="stat"><div class="num">{hz:.0f}Hz</div><div class="label">sample rate (every {sample_interval}frames)</div></div>')
    h.append(f'<div class="stat"><div class="num">{num_buckets}</div><div class="label">{BUCKET}s buckets</div></div>')
    h.append('</div>')

    # 1. Density histogram
    h.append('<h2>Face Density Over Time</h2>')
    h.append('<div style="color:#666;font-size:12px;margin-bottom:4px">Number of face detections per 5-second interval</div>')
    w_px = num_buckets * 2 + 20
    h.append(f'<div class="viz" style="width:{w_px}px;height:80px">')
    for b in range(num_buckets):
        v = density.get(b, 0)
        h_px = max(2, int(60 * v / max(1, max_density * 0.6))) if v > 0 else 0
        if v == 0:
            color = "#0d1117"
        else:
            i = min(v / (max(1, max_density * 0.5)), 1.0)
            r = int(233 * i + 13 * (1 - i))
            g = int(69 * i + 13 * (1 - i))
            bv = int(96 * i + 23 * (1 - i))
            color = f"rgb({r},{g},{bv})"
        title = f"{b * BUCKET:.0f}s: {v} faces"
        h.append(f'<span style="position:absolute;left:{b*2+10}px;bottom:0;width:2px;height:{h_px}px;background:{color}" title="{title}"></span>')
    h.append('</div>')
    h.append(f'<div style="font-size:10px;color:#444;width:{w_px}px;display:flex;justify-content:space-between"><span>0s</span><span>{duration:.0f}s</span></div>')

    # 2. Trace timeline (Gantt)
    h.append('<h2>Trace Timeline</h2>')
    h.append('<div style="color:#666;font-size:12px;margin-bottom:4px">First → last appearance for each trace. Hover for details.</div>')
    show_traces = min(len(trace_spans), 2000)
    bar_h = 2
    chart_height = show_traces * (bar_h + 1) + 10
    h.append(f'<div class="viz" style="width:{w_px}px;height:{chart_height}px">')
    for i, (tid, fn0, fn1, t0, t1, cnt) in enumerate(trace_spans[:show_traces]):
        left = int(t0 / duration * (w_px - 20)) + 10
        width = max(3, int((t1 - t0) / duration * (w_px - 20)))
        top = i * (bar_h + 1) + 5
        opacity = 1.0 if cnt > 5 else 0.3
        identity_note = ""
        if tid in trace_to_identity:
            identity_note = f", identity: {trace_to_identity[tid]}"
        title = f"T{tid}: {t0:.0f}s–{t1:.0f}s, {cnt} faces, f{fn0}–f{fn1}{identity_note}"
        h.append(f'<span class="bar" style="left:{left}px;top:{top}px;width:{width}px;height:{bar_h}px;opacity:{opacity}" title="{title}"></span>')
    h.append('</div>')
    h.append(f'<div style="font-size:10px;color:#444;width:{w_px}px;display:flex;justify-content:space-between"><span>0s (showing {show_traces}/{len(trace_spans)} traces)</span><span>{duration:.0f}s</span></div>')

    # 3. Per-trace heatmap
    h.append('<h2>Per-Trace Heatmap (top 500, every 10th trace)</h2>')
    h.append(f'<div style="overflow-x:scroll;max-width:100%">')
    step = max(1, num_buckets // 120)
    for i, (tid, fn0, fn1, t0, t1, cnt) in enumerate(trace_spans[:500]):
        if i % 10 != 0:
            continue
        start_bkt = int(t0 / BUCKET)
        end_bkt = int(t1 / BUCKET) + 1
        row = f'<div style="white-space:nowrap;line-height:6px"><span style="display:inline-block;width:45px;font-size:6px;color:#555">T{tid}</span>'
        for b in range(0, num_buckets, step):
            active = start_bkt <= b <= end_bkt
            color = "#e94560" if active else "#161b22"
            row += f'<span style="display:inline-block;width:2px;height:4px;background:{color};margin:0"></span>'
        row += '</div>'
        h.append(row)
    h.append('</div>')

    h.append('</body></html>')
    return '\n'.join(h)

html = build_html()
with open(OUT, 'w') as f:
    f.write(html)

print(f"Saved: {OUT}")
print(f"Traces: {len(trace_spans)}, Detections: {total_detections}, Density max: {max_density}, Duration: {duration:.0f}s, Sample: {hz:.0f}Hz")
print(f"Size: {len(html) / 1024:.0f}KB")
