"""Statistical computation utilities for vibelexity."""

from __future__ import annotations

import math


def _percentile(sorted_values, pct):
    """Compute the given percentile from a sorted list of numbers."""
    if not sorted_values:
        return 0
    k = (pct / 100) * (len(sorted_values) - 1)
    f = math.floor(k)
    c = math.ceil(k)
    if f == c:
        return sorted_values[f]
    return sorted_values[f] + (k - f) * (sorted_values[c] - sorted_values[f])


def _compute_stats(scores, reverse=False):
    """Compute average, median, and p95/p99 from a list of complexity scores.

    For reverse=True (metrics where higher = worse like NPath):
    - p95 = worst 5% of NPath scores
    - p99 = worst 1% of NPath scores
    """
    if not scores:
        return {"average": 0, "median": 0, "p95": 0, "p99": 0}
    sorted_scores = sorted(scores, reverse=reverse)
    avg = sum(sorted_scores) / len(sorted_scores)
    median = _percentile(sorted_scores, 50)
    p95 = _percentile(sorted_scores, 5 if reverse else 95)
    p99 = _percentile(sorted_scores, 1 if reverse else 99)
    return {"average": round(avg, 1), "median": median, "p95": p95, "p99": p99}


def _collect_halstead_values(results):
    """Collect per-function Halstead metric values from results."""
    volumes, difficulties, efforts = [], [], []
    for r in results:
        for func in r.functions:
            h = func.halstead
            if h is None:
                continue
            if h.volume > 0:
                volumes.append(h.volume)
            if h.difficulty > 0:
                difficulties.append(h.difficulty)
            if h.effort > 0:
                efforts.append(h.effort)
    return volumes, difficulties, efforts


def _aggregate_halstead(results):
    """Aggregate per-function Halstead metrics into totals and distribution stats."""
    volumes, difficulties, efforts = _collect_halstead_values(results)

    total_effort = sum(efforts)
    return {
        "total_volume": round(sum(volumes), 2),
        "total_effort": round(total_effort, 2),
        "total_time": round(total_effort / 18, 2) if total_effort else 0,
        "total_bugs": round(total_effort ** (2 / 3) / 3000, 4) if total_effort else 0,
        "volume_stats": _compute_stats(volumes),
        "difficulty_stats": _compute_stats(difficulties),
        "effort_stats": _compute_stats(efforts),
    }


def _collect_metric_values(results, attr):
    """Collect non-None per-function values for a given metric attribute."""
    return [
        v
        for r in results
        for func in r.functions
        if (v := getattr(func, attr)) is not None
    ]


def _aggregate_total_max(results, attr, reverse=False):
    """Aggregate a metric into total/max/stats (for NPath, DTD)."""
    values = _collect_metric_values(results, attr)
    if not values:
        return {"total": 0, "max": 0, "stats": _compute_stats([])}
    return {
        "total": sum(values),
        "max": max(values),
        "stats": _compute_stats(values, reverse),
    }


def _aggregate_min_max(results, attr):
    """Aggregate a metric into min/max/stats (for MI, NSI)."""
    values = _collect_metric_values(results, attr)
    if not values:
        return {"min": 0, "max": 0, "stats": _compute_stats([])}
    return {"min": min(values), "max": max(values), "stats": _compute_stats(values)}


def _aggregate_npath(results):
    """Aggregate per-function NPath metrics into totals and distribution stats."""
    return _aggregate_total_max(results, "npath", reverse=True)


def _aggregate_dtd(results):
    """Aggregate per-function DTD metrics into totals and distribution stats."""
    return _aggregate_total_max(results, "dtd")


def _aggregate_cognitive(results):
    """Aggregate per-function Cognitive metrics into totals and distribution stats."""
    return _aggregate_total_max(results, "complexity")


def _aggregate_cyclomatic(results):
    """Aggregate per-function Cyclomatic metrics into totals and distribution stats."""
    return _aggregate_total_max(results, "cyclomatic")


def _compute_duplication_stats(duplicates, all_funcs, total_loc):
    """Compute duplicate functions and lines count with percentages."""
    duplicate_func_locs = []
    for cluster in duplicates:
        for _, func in cluster.members:
            duplicate_func_locs.append(func.func_loc)

    total_functions = len(all_funcs)
    duplicate_count = len(duplicate_func_locs)
    duplicate_pct = (duplicate_count / total_functions * 100) if total_functions else 0

    duplicate_lines = sum(duplicate_func_locs)
    duplicate_lines_pct = (duplicate_lines / total_loc * 100) if total_loc else 0

    return {
        "duplicate_functions": duplicate_count,
        "duplicate_functions_pct": duplicate_pct,
        "duplicate_lines": duplicate_lines,
        "duplicate_lines_pct": duplicate_lines_pct,
    }


def _aggregate_mi(results):
    """Aggregate per-function Maintainability Index into distribution stats."""
    return _aggregate_min_max(results, "mi")


def _aggregate_ldi(results):
    """Aggregate per-function Logic Density Index into distribution stats."""
    return _aggregate_min_max(results, "ldi")
