"""Airspeed Velocity benchmarks for influence DAG traversal.

Measures BFS traversal performance over combined structural tree and
influence DAG edges at varying graph sizes, including cycle detection
overhead.
"""

from __future__ import annotations

from cleveragents.application.services.correction_service import CorrectionService
from cleveragents.domain.models.core.correction import CorrectionMode

# ---------------------------------------------------------------------------
# Graph generators
# ---------------------------------------------------------------------------

_PLAN_ID = "01HGZ6FE0AQDYTR4BXVQZ6EA00"


def _linear_tree(root: str, depth: int) -> dict[str, list[str]]:
    """Build a linear chain of *depth* nodes starting at *root*."""
    tree: dict[str, list[str]] = {}
    current = root
    for i in range(1, depth):
        child = f"{root}_c{i}"
        tree[current] = [child]
        current = child
    return tree


def _star_influence(root: str, fan_out: int) -> dict[str, list[str]]:
    """Build a star influence DAG: root influences *fan_out* nodes."""
    return {root: [f"{root}_inf{i}" for i in range(fan_out)]}


def _chain_influence(root: str, depth: int) -> dict[str, list[str]]:
    """Build a linear chain influence DAG of *depth* nodes."""
    dag: dict[str, list[str]] = {}
    current = root
    for i in range(1, depth):
        nxt = f"{root}_d{i}"
        dag[current] = [nxt]
        current = nxt
    return dag


def _mixed_graph(
    root: str,
    tree_depth: int,
    influence_fan_out: int,
) -> tuple[dict[str, list[str]], dict[str, list[str]]]:
    """Build a combined tree + influence DAG."""
    tree = _linear_tree(root, tree_depth)
    dag = _star_influence(root, influence_fan_out)
    return tree, dag


# ---------------------------------------------------------------------------
# Structural-only BFS benchmarks (baseline)
# ---------------------------------------------------------------------------


class StructuralOnlyBfsSuite:
    """Baseline: BFS over structural tree only (no influence edges)."""

    def setup(self) -> None:
        """Prepare trees and service instances."""
        self.svc = CorrectionService()
        self.small_tree = _linear_tree("D1", 10)
        self.medium_tree = _linear_tree("D1", 100)
        self.large_tree = _linear_tree("D1", 1000)

    def time_small_structural_only(self) -> None:
        """BFS 10-node linear tree, no influence edges."""
        req = self.svc.request_correction(_PLAN_ID, "D1", CorrectionMode.REVERT)
        self.svc.analyze_impact(req.correction_id, self.small_tree)

    def time_medium_structural_only(self) -> None:
        """BFS 100-node linear tree, no influence edges."""
        req = self.svc.request_correction(_PLAN_ID, "D1", CorrectionMode.REVERT)
        self.svc.analyze_impact(req.correction_id, self.medium_tree)

    def time_large_structural_only(self) -> None:
        """BFS 1000-node linear tree, no influence edges."""
        req = self.svc.request_correction(_PLAN_ID, "D1", CorrectionMode.REVERT)
        self.svc.analyze_impact(req.correction_id, self.large_tree)


# ---------------------------------------------------------------------------
# Influence-only BFS benchmarks
# ---------------------------------------------------------------------------


class InfluenceOnlyBfsSuite:
    """BFS over influence DAG only (no structural tree)."""

    def setup(self) -> None:
        """Prepare influence DAGs."""
        self.svc = CorrectionService()
        self.empty_tree: dict[str, list[str]] = {}
        self.small_dag = _chain_influence("D1", 10)
        self.medium_dag = _chain_influence("D1", 100)
        self.large_dag = _chain_influence("D1", 1000)
        self.star_dag = _star_influence("D1", 500)

    def time_small_influence_only(self) -> None:
        """BFS 10-node influence chain."""
        req = self.svc.request_correction(_PLAN_ID, "D1", CorrectionMode.REVERT)
        self.svc.analyze_impact(req.correction_id, self.empty_tree, self.small_dag)

    def time_medium_influence_only(self) -> None:
        """BFS 100-node influence chain."""
        req = self.svc.request_correction(_PLAN_ID, "D1", CorrectionMode.REVERT)
        self.svc.analyze_impact(req.correction_id, self.empty_tree, self.medium_dag)

    def time_large_influence_only(self) -> None:
        """BFS 1000-node influence chain."""
        req = self.svc.request_correction(_PLAN_ID, "D1", CorrectionMode.REVERT)
        self.svc.analyze_impact(req.correction_id, self.empty_tree, self.large_dag)

    def time_star_influence(self) -> None:
        """BFS star DAG with 500 fan-out."""
        req = self.svc.request_correction(_PLAN_ID, "D1", CorrectionMode.REVERT)
        self.svc.analyze_impact(req.correction_id, self.empty_tree, self.star_dag)


# ---------------------------------------------------------------------------
# Combined structural + influence BFS benchmarks
# ---------------------------------------------------------------------------


class CombinedBfsSuite:
    """BFS over combined structural tree + influence DAG."""

    def setup(self) -> None:
        """Prepare combined graphs."""
        self.svc = CorrectionService()
        self.small_tree, self.small_dag = _mixed_graph("D1", 10, 10)
        self.medium_tree, self.medium_dag = _mixed_graph("D1", 100, 50)
        self.large_tree, self.large_dag = _mixed_graph("D1", 500, 200)

    def time_small_combined(self) -> None:
        """BFS combined 10+10 graph."""
        req = self.svc.request_correction(_PLAN_ID, "D1", CorrectionMode.REVERT)
        self.svc.analyze_impact(req.correction_id, self.small_tree, self.small_dag)

    def time_medium_combined(self) -> None:
        """BFS combined 100+50 graph."""
        req = self.svc.request_correction(_PLAN_ID, "D1", CorrectionMode.REVERT)
        self.svc.analyze_impact(req.correction_id, self.medium_tree, self.medium_dag)

    def time_large_combined(self) -> None:
        """BFS combined 500+200 graph."""
        req = self.svc.request_correction(_PLAN_ID, "D1", CorrectionMode.REVERT)
        self.svc.analyze_impact(req.correction_id, self.large_tree, self.large_dag)


# ---------------------------------------------------------------------------
# Cycle detection overhead benchmarks
# ---------------------------------------------------------------------------


class CycleDetectionSuite:
    """Measure overhead of cycle detection in corrupt DAGs."""

    def setup(self) -> None:
        """Prepare DAGs with cycles."""
        self.svc = CorrectionService()
        self.empty_tree: dict[str, list[str]] = {}
        # Small cycle: D1 → D2 → D3 → D1
        self.small_cycle = {"D1": ["D2"], "D2": ["D3"], "D3": ["D1"]}
        # Medium: chain of 50 with cycle at end
        dag: dict[str, list[str]] = {}
        current = "D1"
        for i in range(1, 50):
            nxt = f"D1_cyc{i}"
            dag[current] = [nxt]
            current = nxt
        dag[current] = ["D1"]  # cycle back to start
        self.medium_cycle = dag

    def time_small_cycle(self) -> None:
        """BFS with 3-node cycle."""
        req = self.svc.request_correction(_PLAN_ID, "D1", CorrectionMode.REVERT)
        self.svc.analyze_impact(req.correction_id, self.empty_tree, self.small_cycle)

    def time_medium_cycle(self) -> None:
        """BFS with 50-node chain + cycle."""
        req = self.svc.request_correction(_PLAN_ID, "D1", CorrectionMode.REVERT)
        self.svc.analyze_impact(req.correction_id, self.empty_tree, self.medium_cycle)
