"""CLI handlers for bandito analyze surfaces."""

from __future__ import annotations

import json as json_mod

from .analyze.tradeoffs import ModelEfficiency, TradeoffsResult


def run_drift_cmd(
    project: str,
    *,
    tag: str | None = None,
    last: int | None = None,
    days: int = 14,
    all_traces: bool = False,
    json_output: bool = False,
) -> None:
    from .analyze.drift import run_drift
    from .retrieval import get_retrieval

    try:
        r = get_retrieval(project or "default")
    except Exception as e:
        print(f"\n  Could not load traces: {e}\n")
        return

    scoped = r.with_time_window(days=days) if not all_traces else r
    traces = scoped.traces(tag=tag, limit=last)

    if not traces:
        if json_output:
            print(json_mod.dumps({"error": "no_traces", "project": project}))
        else:
            print(f"\n  No traces for project '{project}'.\n")
        return

    evaluations = scoped.evals()
    result = run_drift(project, traces, evaluations)

    if json_output:
        print(json_mod.dumps(result.to_dict(), indent=2, default=str))
        return

    _print_drift(result)


def _print_drift(result) -> None:
    print(f"\n  Drift Analysis: {result.project}\n")

    parts = [f"{result.trace_count} traces"]
    if result.period_days > 0:
        parts.append(f"{result.period_days} days")
    print(f"  {' · '.join(parts)}")
    print()

    if not result.has_evaluations:
        print("  No evaluation data for quality drift detection.")
        print("  Grade traces to enable quality tracking.\n")
        return

    if result.drift_events:
        print(f"  ── Drift Detected {'─' * 40}")
        print()
        for event in result.drift_events:
            severity_icon = "!" if event.severity == "critical" else "~"
            direction_icon = "↓" if event.direction == "down" else "↑"
            print(f"    {severity_icon} {event.metric.capitalize()} {direction_icon}")
            print(f"      {event.explanation}")
            print(f"      Window: {event.window_start[:10]}")
            print(f"      Traces affected: {event.trace_count}")
            print()
    else:
        print(f"  ── No Drift Detected {'─' * 37}")
        print()
        if result.overall_quality is not None:
            print(f"    Quality:     {result.overall_quality:.0%} pass rate")
        if result.overall_latency_avg is not None:
            print(f"    Latency:     {result.overall_latency_avg:.2f}s avg")
        if result.overall_cost_avg is not None:
            print(f"    Cost:        ${result.overall_cost_avg:.4f} avg")
        if result.last_stable_date:
            print(f"    Last stable: {result.last_stable_date[:10]}")
        print()

    if result.stable_windows > 0:
        print(f"  {result.stable_windows} stable windows.")
        print()

    print("  Next steps:")
    print(f"    bandito tui --project {result.project}  # Grade traces")
    print(f"    bandito diagnostics --project {result.project}")
    print()


def run_coverage_cmd(
    project: str,
    *,
    tag: str | None = None,
    last: int | None = None,
    days: int = 14,
    all_traces: bool = False,
    json_output: bool = False,
) -> None:
    from .analyze.coverage import run_coverage
    from .retrieval import get_retrieval

    try:
        r = get_retrieval(project or "default")
    except Exception as e:
        print(f"\n  Could not load traces: {e}\n")
        return

    scoped = r.with_time_window(days=days) if not all_traces else r
    traces = scoped.traces(tag=tag, limit=last)

    if not traces:
        if json_output:
            print(json_mod.dumps({"error": "no_traces", "project": project}))
        else:
            print(f"\n  No traces for project '{project}'.\n")
        return

    evaluations = scoped.evals()

    calibration_status = None
    try:
        from .api import BanditoClient
        client = BanditoClient()
        calibration_status = client.get_calibration(project)
    except RuntimeError:
        pass

    result = run_coverage(project, traces, evaluations, calibration_status)

    if json_output:
        print(json_mod.dumps(result.to_dict(), indent=2, default=str))
        return

    _print_coverage(result)


def _print_coverage(result) -> None:
    print(f"\n  Coverage Analysis: {result.project}\n")

    parts = [f"{result.trace_count} traces"]
    if result.total_evals > 0:
        parts.append(f"{result.total_evals} graded")
    print(f"  {' · '.join(parts)}")
    print()

    coverage_pct = result.coverage_pct
    if coverage_pct >= 25:
        coverage_bar = "█" * int(coverage_pct / 5) + "░" * (20 - int(coverage_pct / 5))
        print(f"  Eval Coverage: [{coverage_bar}] {coverage_pct:.0f}%")
    else:
        coverage_bar = "░" * 20
        print(f"  Eval Coverage: [{coverage_bar}] {coverage_pct:.0f}%  ⚠ low")
    print()

    if result.blind_spots:
        print(f"  ── Blind Spots {'─' * 42}")
        print()
        for spot in result.blind_spots[:5]:
            pct = spot.pct_of_traces
            bar = "█" * int(pct / 2) + "░" * (50 - int(pct / 2))
            coverage_str = f"{spot.coverage_pct:.0f}%" if spot.coverage_pct > 0 else "0%"
            print(f"    {spot.segment_key} ({spot.trace_count} traces)")
            print(f"      {coverage_str} graded · {pct:.0f}% of traffic")
            print()
    else:
        print(f"  ── Blind Spots {'─' * 42}")
        print()
        print("    No blind spots found. All segments have some evaluation coverage.")
        print()

    if result.calibration:
        print(f"  ── Judge Calibration {'─' * 36}")
        print()
        cal = result.calibration
        if not cal.calibrated:
            print("    Not yet calibrated.")
            print("    Run: bandito judge calibrate --project", result.project)
        else:
            if cal.is_stale:
                print(f"    Status: ⚠ Stale ({cal.days_since_calibration} days old)")
            else:
                print(f"    Status: ✓ Calibrated ({cal.days_since_calibration} days ago)")
            if cal.tpr is not None:
                print(f"    TPR: {cal.tpr:.0%}  TNR: {cal.tnr:.0%}")
            if cal.grades_since_calibration > 0:
                print(f"    {cal.grades_since_calibration} new grades since calibration")
        print()

    print(f"  ── Recommendation {'─' * 38}")
    print()
    for line in result.recommendation.split(". "):
        if line:
            print(f"    {line.strip()}")
    print()


def run_tradeoffs_cmd(
    project: str,
    *,
    tag: str | None = None,
    last: int | None = None,
    days: int = 14,
    all_traces: bool = False,
    json_output: bool = False,
) -> None:
    from .analyze.tradeoffs import run_tradeoffs
    from .retrieval import get_retrieval

    try:
        r = get_retrieval(project or "default")
    except Exception as e:
        print(f"\n  Could not load traces: {e}\n")
        return

    scoped = r.with_time_window(days=days) if not all_traces else r
    traces = scoped.traces(tag=tag, limit=last)

    if not traces:
        if json_output:
            print(json_mod.dumps({"error": "no_traces", "project": project}))
        else:
            print(f"\n  No traces for project '{project}'.\n")
        return

    evaluations = scoped.evals()

    result = run_tradeoffs(project, traces, evaluations)

    if json_output:
        print(json_mod.dumps(result.to_dict(), indent=2, default=str))
        return

    _print_tradeoffs(result)


def _print_tradeoffs(result: TradeoffsResult) -> None:
    print(f"\n  Tradeoffs: {result.project}\n")

    parts = [f"{result.trace_count} traces"]
    if result.model_count > 0:
        parts.append(f"{result.model_count} models")
    if result.has_evaluations:
        parts.append(f"{result.segments_with_evals} segments with evaluations")
    print(f"  {' \u00b7 '.join(parts)}")
    print()

    # -- Model Efficiency --
    if result.models:
        print(f"  \u2500\u2500 Model Efficiency {'─' * 38}")
        print()

        # Header
        has_quality = any(m.quality is not None for m in result.models)
        has_cost = any(m.has_cost for m in result.models)

        # Dynamic column width based on longest model name
        name_width = max(len(_model_name(m)) for m in result.models)
        name_width = max(name_width, 5) + 2  # min "MODEL" + padding

        hdr = f"    {'MODEL':<{name_width}s} {'TRACES':>7s}"
        if has_quality:
            hdr += f" {'QUALITY':>8s}"
        if has_cost:
            hdr += f" {'AVG COST':>10s}"
        hdr += f" {'AVG LAT':>9s}"
        print(hdr)

        for m in result.models:
            line = f"    {_model_name(m):<{name_width}s} {m.trace_count:>7d}"
            if has_quality:
                line += f" {m.quality:>7.0%} " if m.quality is not None else f" {'\u2014':>8s}"
            if has_cost:
                line += f" {'$' + f'{m.avg_cost:.4f}':>10s}" if m.avg_cost is not None else f" {'\u2014':>10s}"
            line += f" {_lat(m.avg_latency):>9s}"
            if m.annotation:
                line += f"  {m.annotation}"
            print(line)

        # Summary comparisons
        if has_quality and has_cost and len(result.models) >= 2:
            print()
            _print_model_comparisons(result.models)

        print()

    # -- Segment Opportunities --
    if result.segment_opportunities:
        print(f"  \u2500\u2500 Segment Opportunities {'─' * 34}")
        print()
        for opp in result.segment_opportunities:
            print(f"    {opp.segment_key} ({opp.trace_count} traces)")
            for m in opp.models:
                parts = []
                if m.quality is not None:
                    parts.append(f"{m.quality:.0%} quality")
                if m.avg_cost is not None:
                    parts.append(f"${m.avg_cost:.4f}")
                print(f"      {m.model:<22s} {' \u00b7 '.join(parts)}")
            if opp.insight:
                print(f"      \u2192 {opp.insight}")
            print()

    # -- Cost Drivers --
    if result.cost_drivers:
        print(f"  \u2500\u2500 Cost Drivers {'─' * 42}")
        print()
        for d in result.cost_drivers:
            print(f"    {d.label}")
            print(f"      {d.detail}")
        print()

    # -- Quality vs Cost --
    qvc = result.quality_vs_cost
    if qvc.has_data:
        print(f"  \u2500\u2500 Quality vs Cost {'─' * 39}")
        print()
        print(f"    Cheaper half:     {qvc.cheap_quality:.0%} avg quality \u00b7 ${qvc.cheap_cost:.4f} avg cost")
        print(f"    Expensive half:   {qvc.expensive_quality:.0%} avg quality \u00b7 ${qvc.expensive_cost:.4f} avg cost")
        gap_pp = qvc.quality_gap * 100
        print(f"    \u2192 {abs(gap_pp):.0f}pp quality gap for {qvc.cost_ratio:.1f}x the cost")
        print()

    # -- Next steps --
    if not result.has_evaluations:
        print(f"  Quality data needed for full tradeoff analysis.")
        print(f"  Grade traces and run the judge first:")
        print(f"    bandito tui --project {result.project}")
        print(f"    bandito judge calibrate --project {result.project}")
        print()
    else:
        print(f"  \u2500\u2500{'─' * 56}")
        print()
        print(f"  Dig deeper:")
        print(f"    bandito analyze drift --project {result.project}")
        print(f"    bandito diagnostics --project {result.project}")
        print()


def _print_model_comparisons(models: list[ModelEfficiency]) -> None:
    """Print one-liner comparisons between models."""
    with_data = [m for m in models if m.quality is not None and m.has_cost and m.avg_cost]
    if len(with_data) < 2:
        return

    # Sort by cost descending
    sorted_models = sorted(with_data, key=lambda m: m.avg_cost, reverse=True)

    for i in range(len(sorted_models) - 1):
        expensive = sorted_models[i]
        cheaper = sorted_models[i + 1]
        quality_diff = expensive.quality - cheaper.quality
        cost_savings = (1 - cheaper.avg_cost / expensive.avg_cost) * 100 if expensive.avg_cost else 0

        if abs(quality_diff) <= 0.05:
            print(f"    {cheaper.model} scores within {abs(quality_diff):.0%} of {expensive.model} at {cost_savings:.0f}% lower cost.")
        elif quality_diff > 0:
            print(f"    {expensive.model} scores {quality_diff:.0%} higher than {cheaper.model} at {cost_savings:.0f}% more cost.")
        else:
            print(f"    {cheaper.model} scores {abs(quality_diff):.0%} higher than {expensive.model} AND costs {cost_savings:.0f}% less.")


def _model_name(m: ModelEfficiency) -> str:
    if m.provider:
        return f"{m.model} ({m.provider})"
    return m.model


def _lat(v: float | None) -> str:
    if v is None:
        return "\u2014"
    if v >= 60:
        return f"{v / 60:.1f}m"
    return f"{v:.2f}s"
