"""Terminal output formatting for review results."""

from __future__ import annotations

from .models import ReviewResult
from .clusterer import ClusteringResult
from .outliers import OutlierReport

_WIDTH = 60

# Human-readable names for pattern detectors
_PATTERN_LABELS = {
    "duplicate_tool_calls": "Duplicate tool calls",
    "cost_concentration": "Cost concentration",
    "latency_concentration": "Latency bottleneck",
    "error_patterns": "Error clusters",
    "context_size_variance": "Context size variance",
    "model_diversity": "Single model",
    "quality_cost_correlation": "Quality vs. cost",
    "quality_by_model": "Quality by model",
}

_SEVERITY_MARKS = {
    "critical": "!!",
    "warning": " ~",
    "info": " i",
}


def format_review(result: ReviewResult) -> str:
    sections = []
    sections.append(_header(result))
    sections.append(_cost_section(result))
    sections.append(_latency_section(result))
    sections.append(_tokens_section(result))
    if result.stats.models:
        sections.append(_models_section(result))
    if result.stats.span_kinds:
        sections.append(_spans_section(result))
    if result.patterns:
        sections.append(_patterns_section(result))

    sections.append(_next_steps(result))
    return "\n".join(sections)


# -- Helpers ------------------------------------------------------------------

def _section_header(title: str) -> str:
    prefix = f"  \u2500\u2500 {title} "
    return prefix + "\u2500" * max(0, _WIDTH - len(prefix))


def _cost(v: float) -> str:
    if v >= 1.0:
        return f"${v:,.2f}"
    return f"${v:.4f}"


def _latency(v: float) -> str:
    if v >= 60:
        return f"{v / 60:.1f}m"
    return f"{v:.2f}s"


def _tokens(v: float) -> str:
    return f"{v:,.0f}"


def _row(label: str, value: str, label2: str = "", value2: str = "") -> str:
    col1 = f"    {label:<9s} {value:<14s}"
    if label2:
        return f"{col1}  {label2:<9s} {value2}"
    return col1


def _filter_hint(result: ReviewResult) -> str:
    parts = []
    if result.config.last_n:
        parts.append(f"last {result.config.last_n}")
    return " \u00b7 ".join(parts) if parts else ""


# -- Sections -----------------------------------------------------------------

def _header(result: ReviewResult) -> str:
    s = result.stats
    project = result.config.project or "all projects"
    cmd = result.command

    lines = [""]
    title_parts = [f"bandito observe {cmd}", project]
    lines.append("  " + " \u00b7 ".join(title_parts))
    lines.append("")

    # Counts line
    count_parts = [f"{s.trace_count} traces", f"{s.span_count} spans"]
    filt = _filter_hint(result)
    if filt:
        count_parts.append(filt)
    lines.append("  " + " \u00b7 ".join(count_parts))

    # Grading status
    grade_parts = []
    if s.human_eval_count:
        h = f"{s.human_eval_count} human-graded"
        if s.human_avg_grade is not None:
            h += f" (avg {s.human_avg_grade:.2f})"
        grade_parts.append(h)
    if s.judge_eval_count:
        j = f"{s.judge_eval_count} judge-scored"
        if s.judge_avg_grade is not None:
            j += f" (avg {s.judge_avg_grade:.2f})"
        grade_parts.append(j)
    if grade_parts:
        lines.append("  " + " · ".join(grade_parts))
    else:
        lines.append("  No grades yet")

    if s.error_count:
        lines.append(f"  {s.error_count} errors")

    lines.append("")
    return "\n".join(lines)


def _cost_section(result: ReviewResult) -> str:
    s = result.stats
    has_data = s.cost.count > 0
    partial = has_data and s.cost.count < s.trace_count

    if partial:
        lines = [_section_header(f"Cost ({s.cost.count} of {s.trace_count} traces)"), ""]
    else:
        lines = [_section_header("Cost"), ""]

    if not has_data:
        lines.append("    No cost data. You can:")
        lines.append("      \u00b7 Pass cost= when creating spans")
        lines.append("      \u00b7 Use a framework mapper (Pydantic AI, Anthropic,")
        lines.append("        OpenAI) \u2014 these extract token counts automatically")
        lines.append("      \u00b7 Auto-pricing from token counts is on the roadmap")
        lines.append("")
        return "\n".join(lines)

    d = s.cost
    lines.append(_row("Total", _cost(d.total), "Avg", _cost(d.mean)))
    lines.append(_row("Median", _cost(d.median), "p95", _cost(d.p95)))
    lines.append(_row("Min", _cost(d.min), "Max", _cost(d.max)))
    if partial:
        missing = s.trace_count - s.cost.count
        lines.append(f"    {missing} traces missing cost data.")
    lines.append("")
    return "\n".join(lines)


def _latency_section(result: ReviewResult) -> str:
    s = result.stats
    lines = [_section_header("Latency"), ""]
    if not s.latency.count:
        lines.append("    No latency data.")
        lines.append("")
        return "\n".join(lines)
    d = s.latency
    lines.append(_row("Avg", _latency(d.mean), "Median", _latency(d.median)))
    lines.append(_row("p95", _latency(d.p95), "Max", _latency(d.max)))
    lines.append("")
    return "\n".join(lines)


def _tokens_section(result: ReviewResult) -> str:
    s = result.stats
    has_data = s.tokens.count > 0
    partial = has_data and s.tokens.count < s.trace_count

    if partial:
        lines = [_section_header(f"Tokens per Trace ({s.tokens.count} of {s.trace_count})"), ""]
    else:
        lines = [_section_header("Tokens per Trace"), ""]

    if not has_data:
        lines.append("    No token data. Most LLM providers include")
        lines.append("    input_tokens/output_tokens in their responses.")
        lines.append("    Framework mappers (Pydantic AI, Anthropic SDK,")
        lines.append("    OpenAI SDK) extract these automatically.")
        lines.append("")
        return "\n".join(lines)

    d = s.tokens
    lines.append(_row("Avg", _tokens(d.mean), "Median", _tokens(d.median)))
    lines.append(_row("p95", _tokens(d.p95), "Max", _tokens(d.max)))
    if partial:
        missing = s.trace_count - s.tokens.count
        lines.append(f"    {missing} traces missing token data.")
    lines.append("")
    return "\n".join(lines)


def _models_section(result: ReviewResult) -> str:
    lines = [_section_header("Models"), ""]
    for m in result.stats.models:
        provider = f" ({m.provider})" if m.provider else ""
        lines.append(f"    {m.model}{provider}")
        details = [f"{m.count} calls"]
        if m.total_cost:
            details.append(_cost(m.total_cost))
        details.append(f"{_latency(m.avg_latency)} avg")
        if m.total_tokens:
            details.append(f"{_tokens(m.total_tokens)} tokens")
        lines.append("      " + " \u00b7 ".join(details))
    lines.append("")
    return "\n".join(lines)


def _spans_section(result: ReviewResult) -> str:
    lines = [_section_header("Span Kinds"), ""]
    for sk in result.stats.span_kinds:
        parts = [f"{sk.count} spans", f"{sk.pct_of_total:.0f}% of latency"]
        if sk.error_count:
            parts.append(f"{sk.error_count} errors")
        lines.append(f"    {sk.kind:<12s} {' \u00b7 '.join(parts)}")
    lines.append("")
    return "\n".join(lines)


def _patterns_section(result: ReviewResult) -> str:
    lines = [_section_header("Patterns"), ""]
    for p in result.patterns:
        mark = _SEVERITY_MARKS.get(p.severity, " ?")
        label = _PATTERN_LABELS.get(p.name, p.name.replace("_", " ").title())
        lines.append(f"  {mark}  {label}")
        lines.append(f"      {p.description}")
        for e in p.evidence[:3]:
            lines.append(f"      \u00b7 {e}")
        lines.append("")
    return "\n".join(lines)


def _next_steps(result: ReviewResult) -> str:
    lines = [_section_header("Next Steps"), ""]
    step = 1
    s = result.stats

    if result.mode == "bootstrap":
        lines.append(f"    {step}. Grade traces to build your quality baseline:")
        lines.append("       bandito evaluate tui")
        lines.append("")
        step += 1
    elif s.human_eval_count and s.trace_count:
        pct = s.human_eval_count / s.trace_count * 100
        if pct < 50:
            lines.append(f"    {step}. Keep grading \u2014 {pct:.0f}% coverage unlocks")
            lines.append("       quality-aware patterns and model comparison:")
            lines.append("       bandito evaluate tui")
            lines.append("")
            step += 1

    lines.append(f"    {step}. Dive deeper into a specific surface:")
    lines.append("       bandito analyze cost")
    lines.append("       bandito analyze errors")
    lines.append("       bandito analyze models")
    lines.append("       bandito analyze         (see all surfaces)")
    lines.append("")

    return "\n".join(lines)


def format_clusters(
    clustering: ClusteringResult,
    outliers: OutlierReport | None = None,
) -> str:
    """Format clustering result for terminal output."""
    lines = []
    lines.append("")
    lines.append(f"  Clusters ({len(clustering.clusters)} groups, {clustering.total_traces} total traces)")
    lines.append("")

    if not clustering.clusters:
        lines.append("  Not enough traces to form clusters.")
        lines.append("  Need at least 3 traces with similar structure.")
        if clustering.total_traces < 10:
            lines.append("  Collect more traces for meaningful analysis.")
        lines.append("")
        return "\n".join(lines)

    for i, cluster in enumerate(clustering.clusters, 1):
        name = cluster.name or cluster.cluster_id
        grade = ""
        if cluster.stats.judge_avg_grade is not None:
            grade = f" | judge {cluster.stats.judge_avg_grade:.2f}"
        elif cluster.stats.human_avg_grade is not None:
            grade = f" | grade {cluster.stats.human_avg_grade:.2f}"

        cost_str = _cost(cluster.stats.cost.mean) if cluster.stats.cost.mean else "-"
        latency_str = _latency(cluster.stats.latency.mean) if cluster.stats.latency.mean else "-"

        lines.append(f"  {i}. {name} ({cluster.size} traces)")
        stats_parts = [f"avg {cost_str}", f"{latency_str}"]
        if grade:
            stats_parts.append(grade.lstrip(" |"))
        lines.append(f"     " + " | ".join(stats_parts))

        outlier_marks = []
        if outliers and any(tid in outliers.structural_outlier_ids for tid in cluster.trace_ids[:1]):
            outlier_marks.append("structurally unusual")
        if outlier_marks:
            lines.append(f"     warning: {', '.join(outlier_marks)}")

    lines.append("")

    if outliers:
        if outliers.structural_outlier_ids:
            lines.append(f"  ⚠ {len(outliers.structural_outlier_ids)} structural outliers ({outliers.outlier_pct:.1f}%)")
            lines.append("")
        elif clustering.total_traces > 0:
            lines.append("  All traces within normal structural range.")
            lines.append("")

    return "\n".join(lines)
