"""Re-summarize + re-embed all thread embeddings with the CURRENT logic.

Why: summaries/vectors were built with the old rule (first outbound reply, which
is often a holding message). The current logic summarizes the LONGEST GENUINE
reply (the real fix, auto-responses excluded). This one-off refreshes every
thread so guide-candidate cards and the clean-resolution filter judge the real
answer. Idempotent: skips threads whose input hash is unchanged.

Parallelised: OpenAI summary+embedding calls run in a thread pool; all DB reads
are done up-front and all writes happen in the main thread.

Usage:
    venv/bin/python scripts/resummarize_embeddings.py [--schema tenant_internal]
                                                       [--limit N] [--workers 12] [--dry-run]
"""
from __future__ import annotations
import argparse
import sys
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from pathlib import Path
from types import SimpleNamespace

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--schema", default="tenant_internal")
    ap.add_argument("--limit", type=int, default=0)
    ap.add_argument("--workers", type=int, default=12)
    ap.add_argument("--dry-run", action="store_true")
    args = ap.parse_args()

    from sqlalchemy import text
    from supporthub.app.db import session_scope, set_worker_tenant_schema
    from supporthub.app.services.embedding_service import (
        EmbeddingService, pack_embedding, compute_text_hash,
    )

    set_worker_tenant_schema(args.schema)
    svc = EmbeddingService()

    # 1. Load embedding rows + thread metadata + all message text (bulk).
    with session_scope(schema=args.schema) as s:
        emb_rows = s.execute(text(
            "SELECT thread_id, text_hash, dimensions FROM thread_embeddings ORDER BY thread_id"
        )).fetchall()
        ids = [r.thread_id for r in emb_rows]
        if args.limit:
            ids = ids[:args.limit]
        old_hash = {r.thread_id: r.text_hash for r in emb_rows}

        meta = {}
        for r in s.execute(text(
            "SELECT id, subject, product_name, ps_version, id_product "
            "FROM addons_threads WHERE id = ANY(:ids)"), {"ids": ids}):
            meta[r.id] = r

        inbound = {}
        for r in s.execute(text(
            "SELECT thread_id, body_text FROM addons_messages "
            "WHERE thread_id = ANY(:ids) AND direction='inbound' AND body_text IS NOT NULL "
            "ORDER BY thread_id, created_at ASC"), {"ids": ids}):
            inbound.setdefault(r.thread_id, []).append(r.body_text)

        longest_out = {}
        for r in s.execute(text(
            "SELECT DISTINCT ON (thread_id) thread_id, body_text FROM addons_messages "
            "WHERE thread_id = ANY(:ids) AND direction='outbound' AND is_auto_response=false "
            "AND body_text IS NOT NULL ORDER BY thread_id, length(body_text) DESC"), {"ids": ids}):
            longest_out[r.thread_id] = r.body_text

    print(f"[{args.schema}] embeddings={len(ids)}", flush=True)

    # 2. Build per-thread inputs, compute new hash, keep only the changed ones.
    work = []
    for tid in ids:
        m = meta.get(tid)
        if not m:
            continue
        thread_ns = SimpleNamespace(id=tid, subject=m.subject, product_name=m.product_name,
                                    ps_version=m.ps_version, id_product=m.id_product)
        inbound_msgs = [SimpleNamespace(body_text=b) for b in inbound.get(tid, [])]
        reply_ns = SimpleNamespace(body_text=longest_out.get(tid)) if tid in longest_out else None
        inbound_text, outbound_text, raw = svc._build_summary_input(thread_ns, inbound_msgs, reply_ns)
        new_hash = compute_text_hash(raw)
        if new_hash == old_hash.get(tid):
            continue
        quality = svc._compute_quality(inbound_text, outbound_text)
        embed_text = svc._build_full_thread_text(inbound_text, outbound_text)
        work.append(dict(tid=tid, thread_ns=thread_ns, inbound_text=inbound_text,
                         outbound_text=outbound_text, quality=quality, embed_text=embed_text,
                         new_hash=new_hash, product_id=m.id_product))

    print(f"changed (to reprocess)={len(work)}  unchanged={len(ids)-len(work)}", flush=True)
    if args.dry_run or not work:
        return

    # 3. Parallel OpenAI: summary + embedding per thread (no DB here).
    def process(item):
        for attempt in range(3):
            try:
                summ = svc._generate_summary(item["thread_ns"], item["inbound_text"],
                                             item["outbound_text"], item["quality"])
                vec = svc._get_embedding(item["embed_text"])
                return item, summ, vec, None
            except Exception as exc:  # transient rate/limit → backoff
                if attempt == 2:
                    return item, None, None, str(exc)
                time.sleep(2 * (attempt + 1))

    results = []
    done = err = 0
    t0 = time.time()
    with ThreadPoolExecutor(max_workers=args.workers) as pool:
        futures = [pool.submit(process, it) for it in work]
        for fut in as_completed(futures):
            item, summ, vec, e = fut.result()
            done += 1
            if e:
                err += 1
                if err <= 5:
                    print(f"  ERROR t{item['tid']}: {e}", flush=True)
            else:
                results.append((item, summ, vec))
            if done % 200 == 0:
                rate = done / max(1e-9, time.time() - t0)
                print(f"  {done}/{len(work)} done ({err} err) ~{rate:.1f}/s", flush=True)

    # 4. Write back in the main thread, in batches.
    print(f"writing {len(results)} rows...", flush=True)
    B = 200
    written = 0
    for i in range(0, len(results), B):
        chunk = results[i:i+B]
        with session_scope(schema=args.schema) as s:
            for item, summ, vec in chunk:
                s.execute(text(
                    "UPDATE thread_embeddings SET text_hash=:h, embedding_blob=:b, "
                    "summary_text=:s, embedding_quality=:q, product_id=:p, "
                    "model_used=:m, created_at=now() WHERE thread_id=:t"),
                    {"h": item["new_hash"], "b": pack_embedding(vec), "s": summ,
                     "q": item["quality"], "p": item["product_id"], "m": svc.model,
                     "t": item["tid"]})
                written += 1
        print(f"  written {written}/{len(results)}", flush=True)

    print(f"DONE: reprocessed={written} errors={err} in {time.time()-t0:.0f}s", flush=True)


if __name__ == "__main__":
    main()
