"""Verify SOFIA v7.1 model scales: param count, memory footprint, forward pass.

Run:
    python -m sofia_v71.verify
    python -m sofia_v71.verify --preset L
    python -m sofia_v71.verify --preset M --benchmark
"""

from __future__ import annotations

import argparse
import time
from typing import Optional

import torch

from .blocks import is_mamba_ssm_available, is_mamba2_available
from .config import sofia_m_config, sofia_l_config, sofia_tiny_config
from .model import SOFIAv71


def count_parameters(model: torch.nn.Module) -> dict:
    """Count parameters per named child."""
    counts = {}
    for name, param in model.named_parameters():
        top = name.split(".")[0]
        counts[top] = counts.get(top, 0) + param.numel()
    counts["_total"] = sum(counts.values())
    return counts


def estimate_quantized_size(n_params: int, precision: str = "int8") -> float:
    """Estimate on-disk / VRAM weight storage in MB."""
    bytes_per_param = {"fp32": 4, "fp16": 2, "int8": 1, "int4": 0.5}[precision]
    return n_params * bytes_per_param / (1024 ** 2)


def test_forward(model: SOFIAv71, device: str = "cpu", batch_size: int = 1) -> dict:
    """Dry forward pass, return output shapes."""
    model = model.to(device).eval()
    cfg = model.cfg
    sat = torch.randn(batch_size, 3, cfg.input_size, cfg.input_size, device=device)
    uav = torch.randn(batch_size, 3, cfg.input_size, cfg.input_size, device=device)
    altitude = torch.rand(batch_size, device=device) * 300.0 + 50.0

    shapes = {}
    with torch.no_grad():
        out = model(sat=sat, uav=uav, altitude=altitude)
    for k, v in out.items():
        if isinstance(v, torch.Tensor):
            shapes[k] = tuple(v.shape)
        elif isinstance(v, list):
            shapes[k] = f"list[{len(v)} × {tuple(v[0].shape)}]"
        elif isinstance(v, dict):
            for kk, vv in v.items():
                if isinstance(vv, torch.Tensor):
                    shapes[f"{k}.{kk}"] = tuple(vv.shape)
    return shapes


def benchmark(model: SOFIAv71, device: str = "cpu", n_warmup: int = 3, n_runs: int = 20) -> dict:
    """Measure forward pass latency."""
    model = model.to(device).eval()
    cfg = model.cfg
    sat = torch.randn(1, 3, cfg.input_size, cfg.input_size, device=device)
    uav = torch.randn(1, 3, cfg.input_size, cfg.input_size, device=device)
    altitude = torch.tensor([150.0], device=device)

    # Warmup
    with torch.no_grad():
        for _ in range(n_warmup):
            _ = model(sat=sat, uav=uav, altitude=altitude)
        if device.startswith("cuda"):
            torch.cuda.synchronize()

        # Measured runs
        times = []
        for _ in range(n_runs):
            if device.startswith("cuda"):
                torch.cuda.synchronize()
            t0 = time.perf_counter()
            _ = model(sat=sat, uav=uav, altitude=altitude)
            if device.startswith("cuda"):
                torch.cuda.synchronize()
            times.append(time.perf_counter() - t0)

    times_ms = [t * 1000 for t in times]
    return {
        "mean_ms": sum(times_ms) / len(times_ms),
        "min_ms": min(times_ms),
        "max_ms": max(times_ms),
        "std_ms": (sum((t - sum(times_ms) / len(times_ms)) ** 2 for t in times_ms) / len(times_ms)) ** 0.5,
    }


def main() -> None:
    parser = argparse.ArgumentParser(description="Verify SOFIA v7.1 model")
    parser.add_argument("--preset", choices=["Tiny", "M", "L"], default="Tiny",
                        help="Model preset (default: Tiny for fast smoke test)")
    parser.add_argument("--device", default="cpu")
    parser.add_argument("--benchmark", action="store_true")
    parser.add_argument("--batch-size", type=int, default=1)
    args = parser.parse_args()

    cfg_fn = {"Tiny": sofia_tiny_config, "M": sofia_m_config, "L": sofia_l_config}[args.preset]
    cfg = cfg_fn()

    # Report mamba_ssm availability
    print(f"\n=== Environment ===")
    print(f"  mamba_ssm v1 (Mamba-1 selective scan): "
          f"{'available' if is_mamba_ssm_available() else 'NOT available'}")
    print(f"  mamba_ssm Mamba-2 (SSD dual form):     "
          f"{'available' if is_mamba2_available() else 'NOT available'}")
    print(f"  configured variant: {cfg.mamba_variant}, backend: {cfg.mamba_backend}")

    print(f"\n=== SOFIA-{args.preset} configuration ===")
    print(cfg.summary())

    print("\n=== Building model ===")
    model = SOFIAv71(cfg)
    counts = count_parameters(model)
    total = counts["_total"]

    print(f"\nTotal params: {total / 1e6:.2f} M")
    print("\nPer-module breakdown:")
    for k, v in sorted(counts.items(), key=lambda x: -x[1]):
        if k == "_total":
            continue
        pct = 100 * v / total
        print(f"  {k:30s} {v / 1e6:>8.3f} M  ({pct:5.1f}%)")

    print("\n=== Memory footprint estimates ===")
    for prec in ["fp32", "fp16", "int8", "int4"]:
        mb = estimate_quantized_size(total, prec)
        print(f"  {prec.upper():6s} weights: {mb:>8.1f} MB  ({mb / 1024:.2f} GB)")

    print("\n=== Forward pass test ===")
    try:
        shapes = test_forward(model, device=args.device, batch_size=args.batch_size)
        for k, v in shapes.items():
            print(f"  out[{k}]: {v}")
    except Exception as e:
        print(f"  ERROR during forward: {type(e).__name__}: {e}")
        import traceback
        traceback.print_exc()

    if args.benchmark:
        print("\n=== Latency benchmark ===")
        try:
            stats = benchmark(model, device=args.device)
            print(f"  mean: {stats['mean_ms']:.2f} ms  min: {stats['min_ms']:.2f}  "
                  f"max: {stats['max_ms']:.2f}  std: {stats['std_ms']:.2f}")
        except Exception as e:
            print(f"  ERROR during benchmark: {type(e).__name__}: {e}")


if __name__ == "__main__":
    main()
