from __future__ import annotations

from dataclasses import dataclass

from .profiler import ProfileNode, RequestTrace


@dataclass(slots=True)
class FlatNode:
    node: ProfileNode
    start_ms: float
    duration_ms: float
    percent_total: float


def _flatten(
    node: ProfileNode,
    root_started_ns: int,
    total_ns: int,
) -> list[FlatNode]:
    flat: list[FlatNode] = []
    duration_ms = node.duration_ns / 1_000_000
    start_ms = (node.started_ns - root_started_ns) / 1_000_000
    percent_total = (
        0.0 if total_ns == 0 else (node.duration_ns / total_ns) * 100
    )
    flat.append(
        FlatNode(
            node=node,
            start_ms=start_ms,
            duration_ms=duration_ms,
            percent_total=percent_total,
        )
    )
    for child in node.children:
        flat.extend(_flatten(child, root_started_ns, total_ns))
    return flat


def _render_bar(
    start_ms: float,
    duration_ms: float,
    total_ms: float,
    width: int,
) -> str:
    if width <= 0:
        return ""

    canvas = [" "] * width
    if total_ms <= 0:
        return "".join(canvas)

    start_col = min(width - 1, int((start_ms / total_ms) * width))
    span_col = max(1, int((duration_ms / total_ms) * width))
    end_col = min(width, start_col + span_col)
    for index in range(start_col, end_col):
        canvas[index] = "#"
    return "".join(canvas)


def _format_label(node: ProfileNode) -> str:
    if node.filename == "<request>":
        return node.name
    return f"{node.filename}:{node.lineno} {node.name}"


def render_trace(
    trace: RequestTrace,
    *,
    width: int = 60,
    hotspot_count: int = 5,
) -> str:
    total_ns = trace.duration_ns
    total_ms = total_ns / 1_000_000
    flat_nodes = _flatten(trace.root, trace.root.started_ns, total_ns)

    non_root_nodes = flat_nodes[1:]
    hottest_node = max(
        non_root_nodes,
        key=lambda item: item.node.self_time_ns,
        default=None,
    )
    hottest_self = sorted(
        non_root_nodes,
        key=lambda item: item.node.self_time_ns,
        reverse=True,
    )[:hotspot_count]

    lines = [
        (
            f"Request {trace.status_code} {trace.method} "
            f"{trace.path} took {total_ms:.2f} ms"
        ),
        "Hotspots",
    ]

    if hottest_self:
        for item in hottest_self:
            lines.append(
                "  "
                f"{item.duration_ms:7.2f} ms total | "
                f"{item.node.self_time_ns / 1_000_000:7.2f} ms self  "
                f"{_format_label(item.node)}"
            )
    else:
        lines.append("  no Python frames were captured")

    lines.append("Waterfall")
    for item in flat_nodes:
        marker = ""
        if hottest_node is not None and item.node is hottest_node.node:
            marker = "  <<< hottest"

        bar = _render_bar(item.start_ms, item.duration_ms, total_ms, width)
        lines.append(
            f"{item.start_ms:8.2f} ms |{bar}|"
            f" {item.duration_ms:8.2f} ms"
            f" {item.percent_total:5.1f}% {_format_label(item.node)}{marker}"
        )

    if trace.dropped_calls:
        lines.append(f"Dropped calls: {trace.dropped_calls}")

    return "\n".join(lines)
