"""Smoke test for DynamicSimilaritySampler: re-embed + batch composition.

Loads checkpoint, embeds a subset of train drones through model.encode_query,
feeds embeddings to the sampler, and verifies:
  - Sampler produces batches
  - Mutex constraint preserved
  - Batches differ from plain mutex (sorted by similarity to seed)
"""
import time
import torch
from torch.utils.data import DataLoader

from src.datasets.dynamic_similarity_sampler import DynamicSimilaritySampler
from src.datasets.gtauav_dataset import (
    GTAUAVDataset,
    GTAUAVDroneQuery,
    collate_drone_query,
)
from src.models.asymmetric_encoder import AsymmetricEncoder, get_dino_transform

CKPT = "out/gtauav/with_text/ckpt_epoch005.pt"
N_SUBSET = 1024  # small subset for smoke speed


def main() -> None:
    model, _ = AsymmetricEncoder.load_checkpoint(CKPT, device="cuda")
    model.eval()

    tf = get_dino_transform(image_size=256)
    ds = GTAUAVDataset(
        pair_json="meta/train_80.json",
        filter_meta="meta/seg_filter.json",
        image_transform=tf,
    )
    # Subset the entries for smoke speed
    ds.entries = ds.entries[:N_SUBSET]
    print(f"Using {len(ds)} train entries")

    query_ds = GTAUAVDroneQuery(ds)
    loader = DataLoader(
        query_ds, batch_size=32, shuffle=False, num_workers=2,
        collate_fn=collate_drone_query, pin_memory=True,
    )

    # Embed
    t0 = time.time()
    embs = []
    with torch.no_grad():
        for batch in loader:
            drone_img = batch["drone_img"].to("cuda", non_blocking=True)
            q = model.encode_query(
                drone_img,
                batch["caption_l1"], batch["caption_l2"], batch["caption_l3"],
            )
            embs.append(q.cpu())
    emb = torch.cat(embs, dim=0)
    print(f"Embedded {emb.shape[0]} queries in {time.time()-t0:.1f}s, dim={emb.shape[1]}")

    # Build sampler
    sat_cand = [e["sat_candidates"] for e in ds.entries]
    sampler = DynamicSimilaritySampler(sat_cand, batch_size=8, seed=42)
    sampler.set_epoch(0)

    # Mutex fallback
    mutex_batches = list(iter(sampler))
    print(f"Mutex-only: {len(mutex_batches)} batches")

    # DSS mode
    sampler.update_embeddings(emb)
    t0 = time.time()
    dss_batches = list(iter(sampler))
    print(f"DSS: {len(dss_batches)} batches in {time.time()-t0:.2f}s")

    # Verify mutex invariant in DSS output
    n_ok = 0
    for b in dss_batches[:20]:
        all_sats = set()
        overlap = False
        for idx in b:
            s = set(ds.entries[idx]["sat_candidates"])
            if s & all_sats:
                overlap = True; break
            all_sats |= s
        if not overlap: n_ok += 1
    print(f"Mutex preserved: {n_ok}/20 DSS batches clean")

    # Similarity check: within a DSS batch, drones should be more similar
    # to each other than drones in a random mutex batch.
    def batch_mean_pairwise_sim(batch):
        e = emb[batch]
        e = torch.nn.functional.normalize(e, dim=-1)
        sim = e @ e.t()
        # Exclude diagonal
        mask = ~torch.eye(len(batch), dtype=torch.bool)
        return sim[mask].mean().item()

    mutex_sim = sum(batch_mean_pairwise_sim(b) for b in mutex_batches[:20]) / 20
    dss_sim = sum(batch_mean_pairwise_sim(b) for b in dss_batches[:20]) / 20
    print(f"Mean in-batch cosine sim — mutex: {mutex_sim:.4f}  DSS: {dss_sim:.4f}")
    print(f"  → DSS batches {'MORE' if dss_sim > mutex_sim else 'LESS'} visually similar (expected: MORE)")


if __name__ == "__main__":
    main()
