"""JSON output formatting for vibelexity."""

from __future__ import annotations

import json
from dataclasses import dataclass
from vibelexity.analysis import (
    _get_metric_val,
    _build_candidate_lists,
    _find_refactor_candidates,
    _all_functions,
    _compute_stats,
    _aggregate_halstead,
    _aggregate_cognitive,
    _aggregate_npath,
    _aggregate_dtd,
    _aggregate_cyclomatic,
    _aggregate_mi,
    _aggregate_ldi,
    _compute_cycle_stats,
)
from vibelexity.call_graph import build_call_graph
from vibelexity.dead_code.formatters import format_json as format_dead_code_json
from vibelexity.metrics.duplication.core import find_duplicates
from vibelexity.metrics.fatness import compute_fatness


@dataclass
class JsonOutputConfig:
    results: list
    duplicates: list
    cycles: list
    dead_code_violations: list | None = None
    source_files: list | None = None
    show_all: bool = False
    show_bottom: bool = False


def _print_json(config: JsonOutputConfig):
    """Print JSON output for the analysis results."""
    output = _create_json_output(config)

    if config.show_bottom:
        all_funcs = _all_functions(config.results)
        _add_json_bottom(output, all_funcs)
    if config.show_all:
        all_funcs = _all_functions(config.results)
        _add_json_all_functions(output, all_funcs)

    dead_code_json = json.loads(
        format_dead_code_json(config.dead_code_violations or [])
    )
    output["dead_code_violations"] = dead_code_json.get("violations", [])
    output["num_dead_code_violations"] = len(output["dead_code_violations"])

    print(json.dumps(output, indent=2))


def _create_json_output(config: JsonOutputConfig):
    """Create the output dictionary for JSON output."""
    all_funcs = _all_functions(config.results)
    scores = [f.complexity for _, f in all_funcs]

    candidate_lists = _build_candidate_lists(all_funcs)
    cycle_stats = _compute_cycle_stats(config.cycles)

    stats_and_mets = _compute_basic_stats(config, all_funcs, scores)
    call_graph = build_call_graph(config.source_files) if config.source_files else None
    fatness_report = compute_fatness(config.results, call_graph=call_graph)

    output = _build_output_dict(
        stats_and_mets, candidate_lists, config, cycle_stats, fatness_report
    )

    return output


def _compute_basic_stats(config, all_funcs, scores):
    """Compute basic statistics for JSON output."""
    return {
        "loc_stats": _compute_stats([r.loc for r in config.results]),
        "lloc_stats": _compute_stats([r.lloc for r in config.results]),
        "sloc_stats": _compute_stats([r.sloc for r in config.results]),
        "complexity_stats": _compute_stats(scores),
    }


def _build_output_dict(stats, candidate_lists, config, cycle_stats, fatness_report):
    """Build the complete output dictionary."""
    results = config.results

    base_output = _build_base_output(results, stats, cycle_stats)
    output = _add_output_details(base_output, candidate_lists, config, fatness_report)
    return output


def _build_base_output(results, stats, cycle_stats):
    """Build the base output dictionary with metrics."""
    return {
        "modules_analyzed": len(results),
        "total_functions": len([f for r in results for f in r.functions]),
        "total_loc": sum(r.loc for r in results),
        "total_lloc": sum(r.lloc for r in results),
        "total_sloc": sum(r.sloc for r in results),
        "halstead": _aggregate_halstead(results),
        "cognitive": _aggregate_cognitive(results),
        "npath": _aggregate_npath(results),
        "dtd": _aggregate_dtd(results),
        "cyclomatic": _aggregate_cyclomatic(results),
        "mi": _aggregate_mi(results),
        "ldi": _aggregate_ldi(results),
        **stats,
        "cycle_length_stats": cycle_stats["cycle_length_stats"],
        "num_cycles": len(cycle_stats.get("files", [])),
    }


def _add_output_details(base_output, candidate_lists, config, fatness_report):
    """Add detailed output like candidate lists and patterns."""
    import importlib

    module = importlib.import_module("vibelexity.output.json")

    output = dict(base_output)
    module._add_json_candidate_lists(output, candidate_lists)
    module._add_json_refactor_candidates(output, candidate_lists)
    module._add_json_patterns(output, config.results)
    module._add_json_fatness(output, fatness_report)
    output["dead_code_violations"] = []
    return output


def _func_to_json(p, f, metric_name):
    """Convert a (path, func) pair to a JSON-serializable dict with one metric."""
    return {
        "file": p,
        "name": f.name,
        "line": f.line,
        metric_name: _get_metric_val(f, metric_name),
    }


def _ldi_to_json(p, f):
    """Convert a (path, func) pair to a JSON dict with LDI + Halstead details."""
    h = f.halstead
    return {
        "file": p,
        "name": f.name,
        "line": f.line,
        "ldi": f.ldi,
        "halstead": {
            "vocabulary": h.vocabulary,
            "volume": h.volume,
            "difficulty": h.difficulty,
            "n2": h.n2,
        }
        if h
        else None,
    }


def _add_json_candidate_lists(output, candidate_lists):
    """Add per-metric top-10 lists to the JSON output dict."""
    json_keys = [
        ("top_refactor_candidates", "complexity"),
        ("top_npath", "npath"),
        ("top_dtd", "dtd"),
        ("top_cyclomatic", "cyclomatic"),
        ("lowest_mi", "mi"),
    ]
    for key, metric in json_keys:
        output[key] = [_func_to_json(p, f, metric) for p, f in candidate_lists[metric]]
    output["top_ldi"] = [_ldi_to_json(p, f) for p, f in candidate_lists["ldi"]]


def _add_json_refactor_candidates(output, candidate_lists):
    """Add top 10 refactor candidates to the JSON output dict."""
    candidates, func_lookup, thresholds = _find_refactor_candidates(candidate_lists)
    if not candidates:
        return
    refactor = []
    for key, names in candidates[:10]:
        p, f = func_lookup[key]
        entry = {"file": p, "name": f.name, "line": f.line, "lists": names}
        for n in names:
            entry[n] = {
                "value": _get_metric_val(f, n),
                "threshold": thresholds.get(n, 0),
            }
        refactor.append(entry)
    output["refactor_candidates"] = refactor


def _add_json_bottom(output, all_funcs):
    """Add lowest-complexity functions to the JSON output dict."""
    bottom = sorted(all_funcs, key=lambda x: x[1].complexity)[:10]
    output["lowest_complexity"] = [
        {"file": p, "name": f.name, "line": f.line, "complexity": f.complexity}
        for p, f in bottom
    ]


def _add_json_all_functions(output, all_funcs):
    """Add all functions sorted by complexity to the JSON output dict."""
    output["functions"] = [
        {
            "file": p,
            "name": f.name,
            "line": f.line,
            "complexity": f.complexity,
            "effort": f.halstead.effort if f.halstead else 0,
            "npath": f.npath,
            "dtd": f.dtd,
            "cyclomatic": f.cyclomatic,
            "mi": f.mi,
            "ldi": f.ldi,
        }
        for p, f in sorted(all_funcs, key=lambda x: x[1].complexity, reverse=True)
    ]


def _add_json_patterns(output, results):
    """Add pattern violations to the JSON output dict."""
    all_violations = []
    for result in results:
        for violation in result.pattern_violations:
            all_violations.append(
                {
                    "file": result.path,
                    "type": violation.type,
                    "line": violation.line,
                    "description": violation.description,
                    "severity": violation.severity,
                }
            )
    output["pattern_violations"] = sorted(all_violations, key=lambda x: x["line"])


def _add_json_fatness(output, fatness_report):
    """Add fatness metrics to the JSON output dict."""
    output["fatness"] = {
        "fat_files": fatness_report.fat_files,
        "fat_functions": fatness_report.fat_functions,
    }


def _add_json_duplicates(output, duplicates):
    """Add duplication clusters to the JSON output dict."""
    type1 = [c for c in duplicates if c.dup_type == 1]
    type2 = [c for c in duplicates if c.dup_type == 2]
    clusters_json = []
    for cluster in duplicates:
        clusters_json.append(
            {
                "type": cluster.dup_type,
                "hash": cluster.hash_value,
                "members": [
                    {"file": fpath, "name": func.name, "line": func.line}
                    for fpath, func in cluster.members
                ],
            }
        )
    output["duplication"] = {
        "type1_clusters": len(type1),
        "type2_clusters": len(type2),
        "clusters": clusters_json,
    }
