"""ASV benchmarks for large-project decomposition overhead.

Measures the time to decompose file sets of various sizes and to
compute dependency closures on synthetic graphs.
"""

from __future__ import annotations

import importlib
import os
import sys
import tempfile
from pathlib import Path

# Ensure the local *source* tree is importable even when ASV has an
# older build of the package installed.
_SRC = str(Path(__file__).resolve().parents[1] / "src")
if _SRC not in sys.path:
    sys.path.insert(0, _SRC)

import cleveragents  # noqa: E402

importlib.reload(cleveragents)

import itertools  # noqa: E402

from cleveragents.application.services.decomposition_graph import (  # noqa: E402
    DependencyClosureComputer,
)
from cleveragents.application.services.decomposition_models import (  # noqa: E402
    DecompositionConfig,
    DependencyEdge,
    DependencyGraph,
    DependencyType,
)
from cleveragents.application.services.decomposition_service import (  # noqa: E402
    DecompositionService,
)


def _build_files(tmpdir: str, count: int, levels: int = 3) -> list[str]:
    """Generate *count* files in *tmpdir* across *levels* dirs."""
    paths: list[str] = []
    for i in range(count):
        level = i % levels
        parts = [f"level{d}" for d in range(level + 1)]
        dirpath = os.path.join(tmpdir, *parts)
        os.makedirs(dirpath, exist_ok=True)
        fpath = os.path.join(dirpath, f"file_{i:06d}.py")
        with open(fpath, "w") as fh:
            fh.write("x" * 200)
        paths.append(fpath)
    return sorted(paths)


def _build_chain_graph(size: int) -> DependencyGraph:
    """Build a chain of *size* nodes."""
    g = DependencyGraph()
    nodes = [f"file_{i:06d}.py" for i in range(size)]
    for n in nodes:
        g.add_node(n)
    for src, tgt in itertools.pairwise(nodes):
        g.add_edge(DependencyEdge(src, tgt, DependencyType.IMPORT))
    return g, nodes  # type: ignore[return-value]


class DecomposeSmallSuite:
    """Benchmark decomposition of a small file set (100 files)."""

    def setup(self) -> None:
        self._tmpdir = tempfile.mkdtemp(prefix="bench-decompose-")
        self._files = _build_files(self._tmpdir, 100)
        self._svc = DecompositionService()

    def time_decompose_default(self) -> None:
        """Decompose 100 files with default config."""
        self._svc.decompose(self._files)


class DecomposeMediumSuite:
    """Benchmark decomposition of a medium file set (1000 files)."""

    def setup(self) -> None:
        self._tmpdir = tempfile.mkdtemp(prefix="bench-decompose-")
        self._files = _build_files(self._tmpdir, 1000)
        self._svc = DecompositionService()

    def time_decompose_default(self) -> None:
        """Decompose 1000 files with default config."""
        self._svc.decompose(self._files)


class DecomposeLargeSuite:
    """Benchmark decomposition of a large file set (5000 files)."""

    def setup(self) -> None:
        self._tmpdir = tempfile.mkdtemp(prefix="bench-decompose-")
        self._files = _build_files(self._tmpdir, 5000, levels=5)
        self._svc = DecompositionService()
        self._config = DecompositionConfig(max_files_per_subplan=200)

    def time_decompose_custom(self) -> None:
        """Decompose 5000 files with custom config."""
        self._svc.decompose(self._files, self._config)


class ClosureSuite:
    """Benchmark dependency closure computation."""

    def setup(self) -> None:
        result = _build_chain_graph(1000)
        self._graph: DependencyGraph = result[0]  # type: ignore[assignment]
        self._roots: list[str] = [result[1][0]]  # type: ignore[index]
        self._computer = DependencyClosureComputer()

    def time_closure_unbounded(self) -> None:
        """Compute unbounded closure on a 1000-node chain."""
        self._computer.clear_cache()
        self._computer.compute_closure(self._graph, self._roots)

    def time_closure_bounded(self) -> None:
        """Compute bounded closure (cutoff=100) on a 1000-node chain."""
        self._computer.clear_cache()
        self._computer.compute_closure(self._graph, self._roots, cutoff=100)

    def time_closure_memoized(self) -> None:
        """Compute closure twice — second should be memoized."""
        self._computer.clear_cache()
        self._computer.compute_closure(self._graph, self._roots)
        self._computer.compute_closure(self._graph, self._roots)
