"""ASV benchmarks for Robot persistence fixture setup overhead.

Measures the cost of initialising file-backed SQLite databases,
seeding prerequisite actions, creating plans, and performing the
close-reopen cycle used in Robot persistence lifecycle tests.
"""

from __future__ import annotations

import tempfile
from datetime import UTC, datetime
from pathlib import Path

from sqlalchemy import create_engine
from sqlalchemy.orm import Session

from cleveragents.domain.models.core.action import (
    Action,
    ActionArgument,
    ActionState,
    ArgumentRequirement,
    ArgumentType,
)
from cleveragents.domain.models.core.plan import (
    AutomationProfileProvenance,
    AutomationProfileRef,
    NamespacedName,
    Plan,
    PlanIdentity,
    PlanPhase,
    PlanTimestamps,
    ProcessingState,
    ProjectLink,
)
from cleveragents.infrastructure.database.models import init_database
from cleveragents.infrastructure.database.repositories import (
    ActionRepository,
    LifecyclePlanRepository,
)

_CB32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"
_bench_counter = 9000


def _bench_ulid() -> str:
    """Generate a monotonically-increasing ULID for benchmarks."""
    global _bench_counter
    _bench_counter += 1
    n: int = _bench_counter
    suffix: str = ""
    for _ in range(8):
        suffix = _CB32[n % 32] + suffix
        n //= 32
    return f"01HGRBT10AQDYTR4RB{suffix}"


def _make_bench_action(name: str = "local/rb-bench-action") -> Action:
    """Create a benchmark action with arguments and invariants."""
    now: datetime = datetime(2026, 2, 1, tzinfo=UTC)
    return Action(
        namespaced_name=NamespacedName.parse(name),
        description="Robot bench action",
        definition_of_done="Bench done",
        strategy_actor="local/strat",
        execution_actor="local/exec",
        arguments=[
            ActionArgument(
                name="target",
                arg_type=ArgumentType.STRING,
                requirement=ArgumentRequirement.REQUIRED,
                description="Target path",
            ),
        ],
        invariants=["No regressions"],
        state=ActionState.AVAILABLE,
        created_at=now,
        updated_at=now,
    )


def _make_bench_plan(
    plan_id: str | None = None,
    action_name: str = "local/rb-bench-action",
) -> Plan:
    """Create a benchmark plan."""
    now: datetime = datetime(2026, 3, 1, tzinfo=UTC)
    return Plan(
        identity=PlanIdentity(plan_id=plan_id or _bench_ulid(), attempt=1),
        namespaced_name=NamespacedName(namespace="local", name="rb-bench-plan"),
        action_name=action_name,
        description="Robot bench plan",
        phase=PlanPhase.ACTION,
        processing_state=ProcessingState.QUEUED,
        automation_profile=AutomationProfileRef(
            profile_name="trusted",
            provenance=AutomationProfileProvenance.PLAN,
        ),
        strategy_actor="local/strat",
        execution_actor="local/exec",
        project_links=[ProjectLink(project_name="local/proj-bench")],
        timestamps=PlanTimestamps(created_at=now, updated_at=now),
        created_by="bench",
        tags=[],
        reusable=True,
        read_only=False,
    )


class TimeRobotFixtureSetupSuite:
    """Benchmark Robot fixture initialisation overhead."""

    timeout = 60

    def setup(self) -> None:
        """Pre-allocate nothing -- each time_* creates its own DB."""

    def time_file_db_init(self) -> None:
        """Measure file-backed SQLite init + schema creation."""
        tmp: str = tempfile.mktemp(suffix=".db")
        try:
            init_database(f"sqlite:///{tmp}")
        finally:
            Path(tmp).unlink(missing_ok=True)

    def time_seed_action(self) -> None:
        """Measure action seeding into a fresh file DB."""
        tmp: str = tempfile.mktemp(suffix=".db")
        try:
            engine = init_database(f"sqlite:///{tmp}")
            session = Session(bind=engine)
            factory = lambda: session  # noqa: E731
            repo = ActionRepository(session_factory=factory)
            repo.create(_make_bench_action())
            session.commit()
            session.close()
        finally:
            Path(tmp).unlink(missing_ok=True)

    def time_seed_plan(self) -> None:
        """Measure plan creation including FK action seeding."""
        tmp: str = tempfile.mktemp(suffix=".db")
        try:
            engine = init_database(f"sqlite:///{tmp}")
            session = Session(bind=engine)
            factory = lambda: session  # noqa: E731
            action_repo = ActionRepository(session_factory=factory)
            action_repo.create(_make_bench_action())
            session.commit()
            plan_repo = LifecyclePlanRepository(session_factory=factory)
            plan_repo.create(_make_bench_plan())
            session.commit()
            session.close()
        finally:
            Path(tmp).unlink(missing_ok=True)


class TimeRobotRestartCycleSuite:
    """Benchmark the close-reopen persistence cycle used in Robot tests."""

    timeout = 60

    def setup(self) -> None:
        """Create file DB with action + plan for restart benchmarks."""
        self._tmp: str = tempfile.mktemp(suffix=".db")
        engine = init_database(f"sqlite:///{self._tmp}")
        session = Session(bind=engine)
        factory = lambda: session  # noqa: E731
        action_repo = ActionRepository(session_factory=factory)
        action_repo.create(_make_bench_action())
        session.commit()
        plan_repo = LifecyclePlanRepository(session_factory=factory)
        self._plan_id: str = _bench_ulid()
        plan_repo.create(_make_bench_plan(self._plan_id))
        session.commit()
        session.close()
        engine.dispose()

    def time_close_reopen_verify(self) -> None:
        """Measure close + reopen + get cycle latency."""
        engine2 = create_engine(f"sqlite:///{self._tmp}", echo=False)
        session2 = Session(bind=engine2)
        factory2 = lambda: session2  # noqa: E731
        repo2 = LifecyclePlanRepository(session_factory=factory2)
        plan = repo2.get(self._plan_id)
        assert plan is not None
        session2.close()
        engine2.dispose()

    def teardown(self) -> None:
        """Clean up temp DB file."""
        Path(self._tmp).unlink(missing_ok=True)
