"""Rich table output formatting for vibelexity."""

from __future__ import annotations

from collections import defaultdict
from dataclasses import dataclass

from rich.console import Console
from rich.panel import Panel
from rich.table import Column, Table
from rich.text import Text

from vibelexity import thresholds
from vibelexity.analysis import (
    _aggregate_cognitive,
    _aggregate_cyclomatic,
    _aggregate_dtd,
    _aggregate_ldi,
    _aggregate_mi,
    _aggregate_npath,
    _build_candidate_lists,
    _compute_stats,
    _find_refactor_candidates,
    _get_metric_val,
)

console = Console(soft_wrap=True, width=120)

_PADDING_STATISTICS = (0, 4, 0, 4)  # top, right, bottom, left
_PADDING = (0, 1, 0, 1)  # top, right, bottom, left


_METRIC_SECTIONS = [
    (
        "Cognitive complexity",
        "Cognitive",
        _aggregate_cognitive,
        "complexity",
        thresholds.COGNITIVE_COMPLEXITY_THRESHOLD,
        "higher",
    ),
    (
        "NPath complexity",
        "NPath",
        _aggregate_npath,
        "npath",
        thresholds.NPATH_THRESHOLD,
        "higher",
    ),
    (
        "Data Transformation Density (DTD)",
        "DTD",
        _aggregate_dtd,
        "dtd",
        thresholds.DTD_THRESHOLD,
        "higher",
    ),
    (
        "Cyclomatic complexity",
        "Cyclomatic",
        _aggregate_cyclomatic,
        "cyclomatic",
        thresholds.CYCLOMATIC_THRESHOLD,
        "higher",
    ),
    (
        "Maintainability Index (MI)",
        "MI",
        _aggregate_mi,
        "mi",
        thresholds.MI_RED_THRESHOLD,
        "lower",
    ),
    (
        "Logic Density Index (LDI)",
        "LDI",
        _aggregate_ldi,
        "ldi",
        thresholds.LDI_THRESHOLD,
        "higher",
    ),
]


def _format_number(value: int | float, compact: bool = False) -> str:
    """Format a number with compact or standard format."""
    if compact and value >= 100_000 and value < 1_000_000:
        scaled = value / 1_000
        return f"{scaled:.1f} × 10³"
    if compact and value >= 1_000_000:
        return "∞"
    return f"{value:,.2f}" if isinstance(value, float) else f"{value:,}"


def _get_cell_style(value, threshold, mode="higher") -> str:
    """Get appropriate style for a cell based on threshold."""
    if threshold == 0:
        return "green"
    violates = (value > threshold) if mode == "higher" else (value < threshold)
    return "bold red" if violates else "green"


def _format_value_with_threshold(value, threshold, mode="higher", compact=False):
    """Format value with threshold warning styling."""
    if threshold == 0:
        return Text(_format_number(value, compact), style="green")

    violates = (value > threshold) if mode == "higher" else (value < threshold)
    style = "bold red" if violates else "green"

    if violates:
        op = ">" if mode == "higher" else "<"
        val_fmt = _format_number(value, compact)
        thresh_fmt = _format_number(threshold, compact)
        return Text.assemble(
            Text(val_fmt, style=style), Text(f" ({op}{thresh_fmt})", style="dim")
        )

    return Text(_format_number(value, compact), style=style)


def print_summary_table(results, all_funcs, total_loc):
    """Print module/function count and line-metric summary."""
    table = Table.grid(padding=(0, 2), expand=True)
    table.add_column(justify="right", style="bold cyan")
    table.add_column()

    table.add_row(
        "Analyzed",
        Text(f"{len(results)} modules, {len(all_funcs)} functions", style="bold"),
    )
    table.add_row("Total lines:", Text(f"{total_loc:,}", style="bold green"))
    table.add_row("Total functions:", Text(f"{len(all_funcs)}", style="bold green"))

    console.print()
    console.print(table)


def print_statistics_table(results, all_funcs):
    """Print complexity, NPath, DTD, cyclomatic, MI, and LDI distributions."""
    console.print()

    table = Table(
        title=Text("Statistics", style="bold cyan"),
        box=None,
        show_header=True,
        show_edge=False,
        show_lines=True,
        header_style="bold yellow",
        pad_edge=True,
        padding=_PADDING_STATISTICS,
    )

    table.add_column("Metric")
    table.add_column("Average", justify="right")
    table.add_column("Median", justify="right")
    table.add_column("P95", justify="right")
    table.add_column("P99", justify="right")

    for title, _, agg_fn, metric_name, threshold, threshold_mode in _METRIC_SECTIONS:
        agg = agg_fn(results)
        use_compact = agg_fn == _aggregate_npath

        avg_cell = _format_value_with_threshold(
            agg["stats"]["average"], threshold, threshold_mode, use_compact
        )
        median_cell = _format_value_with_threshold(
            agg["stats"]["median"], threshold, threshold_mode, use_compact
        )
        p95_cell = _format_value_with_threshold(
            agg["stats"]["p95"], threshold, threshold_mode, use_compact
        )
        p99_cell = _format_value_with_threshold(
            agg["stats"]["p99"], threshold, threshold_mode, use_compact
        )

        table.add_row(Text(title), avg_cell, median_cell, p95_cell, p99_cell)

    table.add_section()
    table.add_row(Text(""), Text(""), Text(""), Text(""), Text(""))
    table.add_section()

    loc_stats = _compute_stats([r.loc for r in results])
    lloc_stats = _compute_stats([r.lloc for r in results])
    sloc_stats = _compute_stats([r.sloc for r in results])

    for name, stats in [
        ("Lines per module → LOC", loc_stats),
        ("Lines per module → LLOC", lloc_stats),
        ("Lines per module → SLOC", sloc_stats),
    ]:
        table.add_row(
            Text(name, style="dim cyan"),
            Text(f"{stats['average']:.2f}", style="green"),
            Text(f"{stats['median']:.2f}", style="green"),
            Text(f"{stats['p95']:.2f}", style="green"),
            Text(f"{stats['p99']:.2f}", style="green"),
        )

    console.print(table)
    console.print()


def print_pattern_violations_table(results, show_bad_patterns=False):
    """Print pattern violations."""
    all_violations = []
    for result in results:
        for violation in result.pattern_violations:
            all_violations.append(
                (result.path, violation.line, violation.type, violation.description)
            )

    if not all_violations:
        console.print(Text("Pattern violations: None", style="bold cyan"))
        return

    violations_by_type = defaultdict(list)
    for path, line, v_type, description in all_violations:
        violations_by_type[v_type].append((path, line, description))

    type_counts = {
        v_type: len(violations) for v_type, violations in violations_by_type.items()
    }

    console.print()

    if show_bad_patterns:
        for v_type in sorted(type_counts.keys()):
            label = v_type.replace("_", " ").title()
            table = Table(
                box=None,
                show_header=False,
                show_edge=False,
                pad_edge=True,
                padding=_PADDING,
            )
            table.add_column()
            table.add_row(Text(f"{label}: {type_counts[v_type]}", style="bold yellow"))
            console.print(table)

            for path, line, description in violations_by_type[v_type]:
                loc_str = f"{path}:{line}"
                console.print(f"  {loc_str:<70} {description}")
    else:
        table = Table(
            title=Text("Pattern violations", style="bold cyan"),
            box=None,
            show_header=True,
            show_edge=False,
            header_style="bold yellow",
            padding=_PADDING,
        )
        table.add_column("Type")
        table.add_column("Count", justify="right")

        table.add_row(
            Text("Total violations", style="yellow"),
            Text(f"{len(all_violations)}", style="green"),
        )

        for v_type in sorted(type_counts.keys()):
            label = v_type.replace("_", " ").title()
            table.add_row(Text(label), Text(str(type_counts[v_type]), style="green"))

        console.print(table)
        console.print(Text("Use --show-bad-patterns to see the full list", style="dim"))


def print_duplication_table(
    duplicates, show_dupes=False, dup_stats=None, all_funcs=None, total_loc=None
):
    """Print duplication clusters."""
    type1 = [c for c in duplicates if c.dup_type == 1]
    type2 = [c for c in duplicates if c.dup_type == 2]

    console.print()

    table = Table(
        title=Text("Code duplication", style="bold cyan"),
        box=None,
        show_header=False,
        show_edge=False,
        pad_edge=True,
        padding=_PADDING,
    )
    table.add_column()

    table.add_row(
        Text(f"Type 1 (exact clones): {len(type1)} cluster(s)", style="yellow")
    )
    table.add_row(
        Text(f"Type 2 (parametric clones): {len(type2)} cluster(s)", style="yellow")
    )

    if dup_stats and all_funcs and total_loc is not None:
        table.add_row(
            Text(
                f"Duplicate lines: {dup_stats['duplicate_lines']}/{total_loc} ({dup_stats['duplicate_lines_pct']:.1f}%)",
                style="yellow",
            )
        )
        table.add_row(
            Text(
                f"Duplicate functions: {dup_stats['duplicate_functions']}/{len(all_funcs)} ({dup_stats['duplicate_functions_pct']:.1f}%)",
                style="yellow",
            )
        )

    console.print(table)

    if show_dupes and duplicates:
        for cluster in duplicates:
            kind = "Type 1 (exact)" if cluster.dup_type == 1 else "Type 2 (parametric)"
            console.print()
            console.print(
                Text(f"  {kind} - {len(cluster.members)} functions:", style="yellow")
            )
            for fpath, func in cluster.members:
                console.print(f"    {fpath}:{func.line} {func.name}")
    elif not show_dupes:
        console.print(Text("Use --show-dupes to see the full list", style="dim"))


def print_dead_code_table(dead_code_violations, show_dead=False):
    """Print dead code violations summary by type."""
    type_counts = defaultdict(int)
    for v in dead_code_violations:
        type_counts[v.type] += 1

    labels = {
        "unused_import": "Unused imports",
        "dead_function": "Dead functions",
        "dead_class": "Dead classes",
        "unused_local_var": "Unused local variables",
    }

    total = len(dead_code_violations)

    console.print()

    table = Table(
        title=Text("Dead code violations", style="bold cyan"),
        box=None,
        show_header=False,
        show_edge=False,
        pad_edge=True,
        padding=_PADDING,
    )
    table.add_column()

    table.add_row(
        Text.assemble(
            Text("Total violations: ", style="yellow"), Text(f"{total}", style="green")
        )
    )

    for v_type in sorted(type_counts.keys()):
        label = labels.get(v_type, v_type.replace("_", " ").title())
        table.add_row(
            Text.assemble(
                Text(f"{label}: ", style="yellow"),
                Text(f"{type_counts[v_type]}", style="green"),
            )
        )

    console.print(table)

    if show_dead:
        for v in dead_code_violations:
            loc = f"{v.path}:{v.line}" if hasattr(v, "line") else v.path
            console.print(f"  {loc} - {v.type}")
    if not show_dead:
        console.print(Text("Use --show-dead to see the full list", style="dim"))


def print_fatness_table(fatness_report, show_fatties=False):
    """Print fatness metrics - files/functions that exceed size thresholds."""
    wide_functions = [f for f in fatness_report.fat_functions if f["param_count"] > 5]
    save_world_functions = [
        f for f in fatness_report.fat_functions if f["call_count"] > 7
    ]

    console.print()

    table = Table(
        title=Text("Fatness metrics", style="bold cyan"),
        box=None,
        show_header=False,
        show_edge=False,
        pad_edge=True,
        padding=_PADDING,
    )
    table.add_column()

    table.add_row(
        Text.assemble(
            Text("Fat files: ", style="yellow"),
            Text(f"{len(fatness_report.fat_files)}", style="green"),
        )
    )
    table.add_row(
        Text.assemble(
            Text("Fat functions: ", style="yellow"),
            Text(f"{len(fatness_report.fat_functions)}", style="green"),
        )
    )
    table.add_row(
        Text.assemble(
            Text("Wide functions (>5 params): ", style="yellow"),
            Text(f"{len(wide_functions)}", style="green"),
        )
    )
    table.add_row(
        Text.assemble(
            Text("Save the world functions (>7 calls): ", style="yellow"),
            Text(f"{len(save_world_functions)}", style="green"),
        )
    )

    console.print(table)

    if show_fatties:
        if fatness_report.fat_files:
            console.print()
            console.print(
                Text("Fat files (>500 LOC or >20 functions):", style="yellow")
            )
            for file_info in fatness_report.fat_files:
                console.print(f"    {file_info['path']} - {file_info['reason']}")

        if fatness_report.fat_functions:
            console.print()
            console.print(
                Text("Fat functions (>50 LOC, >5 params, or >7 calls):", style="yellow")
            )
            for func_info in fatness_report.fat_functions:
                console.print(
                    f"    {func_info['path']}:{func_info['line']} {func_info['name']} - {func_info['reason']}"
                )

    if not show_fatties:
        console.print(Text("Use --show-fatties to see the full list", style="dim"))


def print_refactor_candidates_table(candidate_lists):
    """Print top 10 refactor candidates."""
    candidates, func_lookup, thresholds_map = _find_refactor_candidates(candidate_lists)

    if not candidates:
        console.print(Text("Top 10 Refactor Candidates:", style="bold cyan"))
        console.print(Text("No refactor candidates found", style="dim"))
        return

    console.print(Text("Top 10 Refactor Candidates:", style="bold cyan"))

    table = Table(
        box=None,
        show_header=True,
        show_edge=False,
        show_lines=True,
        header_style="bold yellow",
        padding=_PADDING,
        expand=False,
    )
    table.add_column("Violation", width=50, overflow="fold")
    table.add_column("Location - Function Name", overflow="fold")

    for key, names in candidates[:10]:
        fpath, func = func_lookup[key]
        violation_parts = []
        for n in names:
            val = _get_metric_val(func, n)
            thresh = thresholds_map.get(n, 0)
            op = "<" if n == "mi" else ">"
            val_fmt = _format_number(val, n == "npath")
            thresh_fmt = _format_number(thresh, n == "npath")

            if n == "mi":
                violates = val < thresh
                val_style = "bold red" if violates else "green"
            else:
                violates = val > thresh
                val_style = "bold red" if violates else "green"

            violation_parts.append(
                Text.assemble(
                    Text(f"{n}: ", style="dim"),
                    Text(val_fmt, style=val_style),
                    Text(f" ({op} ", style="dim"),
                    Text(thresh_fmt, style="dim"),
                    Text(")", style="dim"),
                )
            )

        if violation_parts:
            violations_text = Text()
            for i, part in enumerate(violation_parts):
                if i > 0:
                    violations_text.append(", ")
                violations_text.append(part)
        else:
            violations_text = Text("")

        table.add_row(
            violations_text,
            Text(f"{fpath}:{func.line} - {func.name}", style="dim"),
        )

    console.print(table)


def print_worst_metrics_table(metric_name, all_funcs, limit=10):
    """Print worst N metric scores for a specific metric."""
    reverse = metric_name != "mi"

    metric_labels = {
        "complexity": "Cognitive Complexity",
        "npath": "NPath Complexity",
        "dtd": "Data Transformation Density",
        "cyclomatic": "Cyclomatic Complexity",
        "mi": "Maintainability Index",
        "ldi": "Logic Density Index",
    }

    title = f"Worst {limit} {metric_labels.get(metric_name, metric_name)}"

    console.print()

    sorted_funcs = sorted(
        all_funcs, key=lambda x: _get_metric_val(x[1], metric_name), reverse=reverse
    )[:limit]

    max_loc_len = (
        max(len(f"{fpath}:{func.line}") for fpath, func in sorted_funcs)
        if sorted_funcs
        else 0
    )

    table = Table(
        title=Text(title, style="bold cyan"),
        box=None,
        show_header=True,
        show_edge=False,
        show_lines=True,
        header_style="bold yellow",
        padding=_PADDING,
        expand=True,
    )
    table.add_column("Value", width=6, overflow="fold")
    table.add_column("Location - Function Name", overflow="fold")

    for fpath, func in sorted_funcs:
        val = _get_metric_val(func, metric_name)
        val_str = _format_number(val, metric_name == "npath")
        style = (
            "bold red"
            if (
                (metric_name != "mi" and val > thresholds.NPATH_THRESHOLD)
                or (metric_name == "mi" and val < thresholds.MI_RED_THRESHOLD)
            )
            else "green"
        )

        table.add_row(
            Text(val_str, style=style),
            Text(f"{fpath}:{func.line} - {func.name}", style="dim"),
        )

    console.print(table)


def print_cycle_stats_table(cycle_stats, show_circulars=False):
    """Print circular import cycle statistics."""
    num_cycles = cycle_stats.get("num_cycles", 0)
    stats = cycle_stats.get("cycle_length_stats", _compute_stats([]))

    console.print()
    console.print(Text("Circular import cycles:", style="bold cyan"))

    table = Table(
        box=None,
        show_header=False,
        show_edge=False,
        pad_edge=True,
        padding=_PADDING,
    )
    table.add_column()

    table.add_row(Text(f"Number of cycles detected: {num_cycles}", style="yellow"))

    if num_cycles > 0:
        stats_str = (
            f"Avg: {stats['average']:.2f}, Median: {stats['median']:.2f}, "
            f"P95: {stats['p95']:.2f}, P99: {stats['p99']:.2f}"
        )
        table.add_row(
            Text(f"Cycle length statistics (nodes per cycle):", style="yellow")
        )
        table.add_row(Text(f"  {stats_str}", style="green"))

    console.print(table)

    if show_circulars and num_cycles > 0:
        sum_cycles_per_file = cycle_stats.get("sum_cycles_per_file", {})
        if sum_cycles_per_file:
            console.print()
            console.print(Text("Cycles per file:", style="yellow"))
            for file_path, count in sorted(sum_cycles_per_file.items()):
                console.print(f"    {file_path}: {count}")
    elif not show_circulars:
        console.print(Text("Use --show-circulars to see cycle details", style="dim"))
