"""ASV benchmarks for resource DAG link and auto_discover."""

from __future__ import annotations

import contextlib
import typing
from datetime import UTC, datetime
from typing import Any


def _setup_db() -> Any:
    """Create in-memory database and return session factory."""
    from sqlalchemy import create_engine, event
    from sqlalchemy.orm import sessionmaker

    from cleveragents.infrastructure.database.models import Base

    engine = create_engine("sqlite:///:memory:")

    @event.listens_for(engine, "connect")
    def _fk(conn: Any, _rec: Any) -> None:
        conn.cursor().execute("PRAGMA foreign_keys=ON")

    Base.metadata.create_all(engine)
    return sessionmaker(bind=engine)


_CB32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
_BENCH_CTR = 0


def _bench_ulid() -> str:
    global _BENCH_CTR
    _BENCH_CTR += 1
    n = _BENCH_CTR
    suffix = ""
    for _ in range(7):
        suffix = _CB32[n % 32] + suffix
        n //= 32
    return f"01HDAGBNCH0AQDYTR4B{suffix}"[:26]


def _seed_types(
    rt_repo: Any,
    session_factory: Any,
    parent_name: str = "bench/dag-parent",
    child_name: str = "bench/dag-child",
) -> None:
    """Seed parent and child resource types."""
    from cleveragents.domain.models.core.resource_type import (
        ResourceKind,
        ResourceTypeSpec,
        SandboxStrategy,
    )

    parent_spec = ResourceTypeSpec(
        name=parent_name,
        description="Bench parent type",
        resource_kind=ResourceKind.PHYSICAL,
        sandbox_strategy=SandboxStrategy.NONE,
        user_addable=True,
        cli_args=[],
        parent_types=[],
        child_types=[child_name],
        auto_discovery={
            "enabled": True,
            "rules": [{"type": child_name, "pattern": "*"}],
        },
        equivalence=None,
        handler=None,
        capabilities={
            "read": True,
            "write": True,
            "sandbox": True,
            "checkpoint": False,
        },
        built_in=False,
    )
    child_spec = ResourceTypeSpec(
        name=child_name,
        description="Bench child type",
        resource_kind=ResourceKind.PHYSICAL,
        sandbox_strategy=SandboxStrategy.NONE,
        user_addable=True,
        cli_args=[],
        parent_types=[],
        child_types=[],
        auto_discovery=None,
        equivalence=None,
        handler=None,
        capabilities={
            "read": True,
            "write": True,
            "sandbox": True,
            "checkpoint": False,
        },
        built_in=False,
    )
    rt_repo.create(parent_spec)
    if child_name != parent_name:
        rt_repo.create(child_spec)
    # Commit so types survive across session boundaries (StaticPool).
    session_factory().commit()


def _seed_single_type(
    rt_repo: Any,
    type_name: str = "bench/chain-type",
) -> None:
    """Seed a single self-linking resource type for DAG chain benchmarks."""
    from cleveragents.domain.models.core.resource_type import (
        ResourceKind,
        ResourceTypeSpec,
        SandboxStrategy,
    )

    spec = ResourceTypeSpec(
        name=type_name,
        description="Bench chain type",
        resource_kind=ResourceKind.PHYSICAL,
        sandbox_strategy=SandboxStrategy.NONE,
        user_addable=True,
        cli_args=[],
        parent_types=[],
        child_types=[type_name],
        auto_discovery=None,
        equivalence=None,
        handler=None,
        capabilities={
            "read": True,
            "write": True,
            "sandbox": True,
            "checkpoint": False,
        },
        built_in=False,
    )
    rt_repo.create(spec)


def _make_resource(
    res_repo: Any,
    type_name: str,
    session_factory: Any,
) -> str:
    """Create a resource and return its ID."""
    from cleveragents.domain.models.core.resource import (
        PhysVirt,
        Resource,
        ResourceCapabilities,
    )

    rid = _bench_ulid()
    res = Resource(
        resource_id=rid,
        name=None,
        resource_type_name=type_name,
        classification=PhysVirt.PHYSICAL,
        description="Bench resource",
        properties={},
        location=None,
        content_hash=None,
        sandbox_strategy=None,
        capabilities=ResourceCapabilities(),
        created_at=datetime.now(tz=UTC),
        updated_at=datetime.now(tz=UTC),
    )
    res_repo.create(res)
    # Commit so the resource survives across session boundaries.
    session_factory().commit()
    return rid


class TimeLinkChild:
    """Benchmark ResourceRepository.link_child()."""

    def setup(self) -> None:
        from cleveragents.infrastructure.database.repositories import (
            ResourceRepository,
            ResourceTypeRepository,
        )

        self.factory = _setup_db()
        self.rt_repo = ResourceTypeRepository(self.factory)
        self.res_repo = ResourceRepository(self.factory)
        _seed_types(self.rt_repo, self.factory)
        self.parent_id = _make_resource(self.res_repo, "bench/dag-parent", self.factory)
        self._child_ctr = 0

    def time_link_child(self) -> None:
        child_id = _make_resource(self.res_repo, "bench/dag-child", self.factory)
        self.res_repo.link_child(self.parent_id, child_id)


class TimeUnlinkChild:
    """Benchmark ResourceRepository.unlink_child()."""

    def setup(self) -> None:
        from cleveragents.infrastructure.database.repositories import (
            ResourceRepository,
            ResourceTypeRepository,
        )

        self.factory = _setup_db()
        self.rt_repo = ResourceTypeRepository(self.factory)
        self.res_repo = ResourceRepository(self.factory)
        _seed_types(self.rt_repo, self.factory)
        self.parent_id = _make_resource(self.res_repo, "bench/dag-parent", self.factory)
        self.child_id = _make_resource(self.res_repo, "bench/dag-child", self.factory)
        self.res_repo.link_child(self.parent_id, self.child_id)
        self.factory().commit()

    def time_unlink_child(self) -> None:
        self.res_repo.unlink_child(self.parent_id, self.child_id)
        # Re-link so the benchmark can run multiple times
        self.res_repo.link_child(self.parent_id, self.child_id)


class TimeAutoDiscoverChildren:
    """Benchmark ResourceRepository.auto_discover_children()."""

    def setup(self) -> None:
        from cleveragents.infrastructure.database.repositories import (
            ResourceRepository,
            ResourceTypeRepository,
        )

        self.factory = _setup_db()
        self.rt_repo = ResourceTypeRepository(self.factory)
        self.res_repo = ResourceRepository(self.factory)
        _seed_types(self.rt_repo, self.factory)
        self.parent_id = _make_resource(self.res_repo, "bench/dag-parent", self.factory)

    def time_auto_discover_children(self) -> None:
        self.res_repo.auto_discover_children(self.parent_id)


class TimeCycleDetection:
    """Benchmark cycle detection with deep DAG chains."""

    params: typing.ClassVar[list[int]] = [5, 10, 50]
    param_names: typing.ClassVar[list[str]] = ["chain_depth"]

    def setup(self, chain_depth: int) -> None:
        from cleveragents.infrastructure.database.repositories import (
            ResourceRepository,
            ResourceTypeRepository,
        )

        self.factory = _setup_db()
        self.rt_repo = ResourceTypeRepository(self.factory)
        self.res_repo = ResourceRepository(self.factory)
        _seed_single_type(self.rt_repo, "bench/chain-type")

        # Build a chain: r0 -> r1 -> ... -> rN
        self.chain_ids: list[str] = []
        for _ in range(chain_depth + 1):
            rid = _make_resource(self.res_repo, "bench/chain-type", self.factory)
            self.chain_ids.append(rid)

        for i in range(chain_depth):
            self.res_repo.link_child(
                self.chain_ids[i],
                self.chain_ids[i + 1],
            )
        self.factory().commit()

    def time_cycle_detection(self, chain_depth: int) -> None:
        """Attempt to close the cycle (should raise)."""
        with contextlib.suppress(Exception):
            self.res_repo.link_child(
                self.chain_ids[-1],
                self.chain_ids[0],
            )
