"""Analysis and aggregation utilities for vibelexity."""

from __future__ import annotations

import sys
from pathlib import Path
from typing import TYPE_CHECKING

from vibelexity import thresholds
from vibelexity.stats import (
    _compute_stats,
    _aggregate_halstead,
    _aggregate_npath,
    _aggregate_dtd,
    _aggregate_cognitive,
    _aggregate_cyclomatic,
    _aggregate_mi,
    _aggregate_ldi,
    _compute_duplication_stats,
)

if TYPE_CHECKING:
    from vibelexity.models import FileComplexity, FunctionComplexity


def _analyze_files(filepaths):
    from vibelexity.analyzers.go import analyze_file as analyze_file_go
    from vibelexity.analyzers.python import analyze_file as analyze_file_py
    from vibelexity.analyzers.typescript import analyze_file as analyze_file_ts

    _TS_EXTENSIONS = {".ts", ".tsx", ".js", ".jsx"}
    _GO_EXTENSIONS = {".go"}

    results = []
    for filepath in filepaths:
        ext = Path(filepath).suffix.lower()
        if ext in _TS_EXTENSIONS:
            analyzer = analyze_file_ts
        elif ext in _GO_EXTENSIONS:
            analyzer = analyze_file_go
        else:
            analyzer = analyze_file_py
        try:
            result = analyzer(filepath)
        except Exception as e:
            print(f"Warning: skipping {filepath}: {e}", file=sys.stderr)
            continue
        results.append(result)
    return results


def _all_functions(results):
    """Collect all (file_path, FunctionComplexity) tuples from results."""
    funcs = []
    for result in results:
        for func in result.functions:
            funcs.append((result.path, func))
    return funcs


def _get_metric_val(func, name):
    """Extract a metric value from a FunctionComplexity by metric name."""
    if name == "complexity":
        return func.complexity
    if name == "effort":
        return func.halstead.effort if func.halstead else 0
    if name == "npath":
        return func.npath if func.npath is not None else 0
    if name == "dtd":
        return func.dtd if func.dtd is not None else 0
    if name == "cyclomatic":
        return func.cyclomatic if func.cyclomatic is not None else 0
    if name == "mi":
        return func.mi if func.mi is not None else 0
    if name == "ldi":
        return func.ldi if func.ldi is not None else 0
    return 0


def _sort_worst(all_funcs, key_name, n=10, reverse=True):
    """Sort functions by a metric and return the top/bottom N."""
    default = 0 if key_name != "mi" else 100.0
    return sorted(
        all_funcs,
        key=lambda x: (
            _get_metric_val(x[1], key_name)
            if _get_metric_val(x[1], key_name) is not None
            else default
        ),
        reverse=reverse,
    )[:n]


def _build_candidate_lists(all_funcs):
    """Build all six ranked candidate lists."""
    candidates = {
        "complexity": [
            f
            for f in all_funcs
            if _get_metric_val(f[1], "complexity")
            > thresholds.COGNITIVE_COMPLEXITY_THRESHOLD
        ],
        "npath": [
            f
            for f in all_funcs
            if f[1].npath and f[1].npath > thresholds.NPATH_THRESHOLD
        ],
        "dtd": [
            f for f in all_funcs if f[1].dtd and f[1].dtd > thresholds.DTD_THRESHOLD
        ],
        "cyclomatic": [
            f
            for f in all_funcs
            if f[1].cyclomatic and f[1].cyclomatic > thresholds.CYCLOMATIC_THRESHOLD
        ],
        "mi": [
            f for f in all_funcs if f[1].mi and f[1].mi < thresholds.MI_RED_THRESHOLD
        ],
        "ldi": [
            f for f in all_funcs if f[1].ldi and f[1].ldi > thresholds.LDI_THRESHOLD
        ],
    }
    candidates["complexity"] = _sort_worst(candidates["complexity"], "complexity")
    candidates["npath"] = _sort_worst(candidates["npath"], "npath")
    candidates["dtd"] = _sort_worst(candidates["dtd"], "dtd")
    candidates["cyclomatic"] = _sort_worst(candidates["cyclomatic"], "cyclomatic")
    candidates["mi"] = _sort_worst(candidates["mi"], "mi", reverse=False)
    candidates["ldi"] = _sort_worst(candidates["ldi"], "ldi")
    return candidates


def _find_refactor_candidates(candidate_lists):
    """Find refactor candidates, sorted by violation count and severity."""
    metric_thresholds = {}
    threshold_map = {
        "complexity": thresholds.COGNITIVE_COMPLEXITY_THRESHOLD,
        "npath": thresholds.NPATH_THRESHOLD,
        "dtd": thresholds.DTD_THRESHOLD,
        "cyclomatic": thresholds.CYCLOMATIC_THRESHOLD,
        "mi": thresholds.MI_RED_THRESHOLD,
        "ldi": thresholds.LDI_THRESHOLD,
    }
    for list_name, cl in candidate_lists.items():
        if cl:
            metric_thresholds[list_name] = threshold_map.get(list_name, 0)

    memberships = {}
    func_lookup = {}
    for list_name, cl in candidate_lists.items():
        for p, f in cl:
            key = (p, f.name, f.line)
            memberships.setdefault(key, []).append(list_name)
            func_lookup[key] = (p, f)

    def _severity_score(key, names):
        p, f = func_lookup[key]
        max_ratio = 0
        for n in names:
            val = _get_metric_val(f, n)
            thresh = threshold_map.get(n, 0)
            if thresh != 0:
                ratio = val / thresh
                if n == "mi":
                    ratio = 1 / ratio if ratio != 0 else 0
                max_ratio = max(max_ratio, ratio)
        return max_ratio

    candidates = [(key, names) for key, names in memberships.items()]
    candidates.sort(
        key=lambda x: (len(x[1]), _severity_score(x[0], x[1])), reverse=True
    )
    return candidates, func_lookup, metric_thresholds


def _compute_cycle_stats(cycles):
    if not cycles:
        return {
            "cycle_length_stats": _compute_stats([]),
            "sum_cycles_per_file": {},
            "num_cycles": 0,
        }

    cycle_lengths = [len(cycle.files) for cycle in cycles]

    cycle_length_stats = _compute_stats(cycle_lengths)

    sum_cycles_per_file = {}
    for cycle in cycles:
        for file in cycle.files:
            sum_cycles_per_file[file] = sum_cycles_per_file.get(file, 0) + len(
                cycle.files
            )

    return {
        "cycle_length_stats": cycle_length_stats,
        "sum_cycles_per_file": sum_cycles_per_file,
        "num_cycles": len(cycles),
    }
