"""Dependency injection container for CleverAgents.

Based on ADR-003 (Dependency Injection Framework).
Uses dependency-injector for managing service instances.
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from sqlalchemy.orm import Session, sessionmaker

import structlog
from dependency_injector import containers, providers

from cleveragents.actor.registry import ActorRegistry
from cleveragents.application.services.acms_service import ACMSPipeline
from cleveragents.application.services.actor_service import ActorService
from cleveragents.application.services.audit_event_subscriber import (
    AuditEventSubscriber,
)
from cleveragents.application.services.audit_service import AuditService
from cleveragents.application.services.automation_profile_service import (
    AutomationProfileService,
)
from cleveragents.application.services.autonomy_controller import (
    AutonomyController,
)
from cleveragents.application.services.autonomy_guardrail_service import (
    AutonomyGuardrailService,
)
from cleveragents.application.services.checkpoint_service import CheckpointService
from cleveragents.application.services.config_service import ConfigService
from cleveragents.application.services.context_service import ContextService
from cleveragents.application.services.context_tiers import (
    ContextTierService,
)
from cleveragents.application.services.correction_service import CorrectionService
from cleveragents.application.services.cost_budget_service import CostBudgetService
from cleveragents.application.services.decision_service import DecisionService
from cleveragents.application.services.decomposition_service import (
    DecompositionService,
)
from cleveragents.application.services.execution_environment_resolver import (
    ExecutionEnvironmentResolver,
)
from cleveragents.application.services.faiss_vector_backend import (
    build_vector_backend,
    build_vector_index_backend,
)
from cleveragents.application.services.fix_then_revalidate import (
    FixThenRevalidateOrchestrator,
)
from cleveragents.application.services.invariant_service import InvariantService
from cleveragents.application.services.multi_project_service import (
    MultiProjectService,
)
from cleveragents.application.services.plan_lifecycle_service import (
    PlanLifecycleService,
)
from cleveragents.application.services.plan_service import PlanService
from cleveragents.application.services.project_service import ProjectService
from cleveragents.application.services.repo_indexing_service import (
    RepoIndexingService,
)
from cleveragents.application.services.resource_file_watcher import (
    ResourceFileWatcher,
)
from cleveragents.application.services.resource_registry_service import (
    ResourceRegistryService,
)
from cleveragents.application.services.session_service import (
    PersistentSessionService,
)
from cleveragents.application.services.skeleton_compressor import (
    SkeletonCompressorService,
)
from cleveragents.application.services.skill_service import SkillService
from cleveragents.application.services.subplan_service import SubplanService
from cleveragents.application.services.tool_registry_service import ToolRegistryService
from cleveragents.application.services.trace_service import TraceService
from cleveragents.application.services.uko_indexer import UKOIndexer
from cleveragents.application.services.vector_store_service import VectorStoreService
from cleveragents.config.settings import Settings, get_settings
from cleveragents.domain.models.acms.analyzers import AnalyzerRegistry
from cleveragents.domain.models.acms.index_stubs import (
    InMemoryGraphIndexBackend,
    InMemoryTextIndexBackend,
)
from cleveragents.domain.models.acms.stubs import (
    InMemoryGraphBackend,
    InMemoryTextBackend,
)
from cleveragents.domain.providers.ai_provider import AIProviderInterface
from cleveragents.infrastructure.database.llm_trace_repository import (
    LLMTraceRepository,
)
from cleveragents.infrastructure.database.repositories import (
    CheckpointRepository,
    NamespacedProjectRepository,
    ProjectResourceLinkRepository,
    SessionMessageRepository,
    SessionRepository,
)
from cleveragents.infrastructure.database.unit_of_work import UnitOfWork
from cleveragents.infrastructure.events.reactive import ReactiveEventBus
from cleveragents.infrastructure.observability.metrics_emitter import MetricsEmitter
from cleveragents.infrastructure.plugins.extension_catalog import (
    register_all_extension_points,
)
from cleveragents.infrastructure.plugins.manager import PluginManager
from cleveragents.providers.registry import ProviderRegistry, get_provider_registry
from cleveragents.shared.redaction import redact_value
from cleveragents.tui.persona.registry import PersonaRegistry
from cleveragents.tui.persona.state import PersonaState

_logger = structlog.get_logger(__name__)


def _build_stream_router():
    """Build a ReactiveStreamRouter, importing lazily to avoid heavy deps."""
    from cleveragents.reactive.stream_router import ReactiveStreamRouter

    return ReactiveStreamRouter()


def _build_langgraph_bridge(stream_router):
    """Build the RxPyLangGraphBridge, importing lazily."""
    from cleveragents.langgraph.bridge import RxPyLangGraphBridge

    return RxPyLangGraphBridge(stream_router=stream_router)


def _build_route_bridge(stream_router, agents):
    """Build the RouteBridge, importing lazily."""
    from cleveragents.reactive.route_bridge import RouteBridge

    return RouteBridge(stream_router=stream_router, agents=agents)


def _lazy_register_registry_agents(stream_router, route_bridge, actor_registry):
    """Lazily import and call register_registry_agents."""
    from cleveragents.application.reactive_registry_adapter import (
        register_registry_agents,
    )

    return register_registry_agents(stream_router, route_bridge, actor_registry)


@dataclass(slots=True)
class TuiServiceAdapters:
    """Lightweight adapter bundle for TUI-facing read operations.

    The TUI is presentation-layer only; this keeps integration explicit
    without introducing TUI concepts into domain models.
    """

    actor_service: ActorService
    session_service: PersistentSessionService
    project_service: ProjectService
    resource_registry_service: ResourceRegistryService
    plan_service: PlanService


def get_ai_provider(
    settings: Settings | None = None,
    provider_registry: ProviderRegistry | None = None,
) -> AIProviderInterface | None:
    """Build the AI provider based on runtime configuration.

    Uses ``Settings.mock_providers`` to decide whether to load the mock
    provider.  The raw ``CLEVERAGENTS_TESTING_USE_MOCK_AI`` env-var is
    still honoured as a fallback for backward-compatibility but the
    settings flag is checked first.
    """

    import os

    resolved_settings = settings or get_settings()
    resolved_registry = provider_registry or get_provider_registry(resolved_settings)

    # Prefer the validated settings flag; fall back to raw env-var for compat
    use_mock_ai = resolved_settings.mock_providers or (
        os.environ.get("CLEVERAGENTS_TESTING_USE_MOCK_AI", "").lower()
        in ("true", "1", "yes")
    )

    if use_mock_ai:
        try:
            import sys
            from pathlib import Path

            features_path = Path(__file__).parent.parent.parent.parent / "features"
            if features_path.exists() and str(features_path) not in sys.path:
                sys.path.insert(0, str(features_path))

            from mocks.mock_ai_provider import MockAIProvider  # type: ignore

            return MockAIProvider()  # type: ignore
        except ImportError:
            return None

    if not resolved_registry.get_configured_providers():
        return None

    return resolved_registry.create_ai_provider()


def get_database_url() -> str:
    """Get the database URL with proper path handling.

    Prefers explicit test overrides to keep CI fast and non-interactive.

    Returns:
        SQLAlchemy database URL with absolute path for SQLite
    """
    import os

    for key in (
        "CLEVERAGENTS_DATABASE_URL",
        "CLEVERAGENTS_TEST_DATABASE_URL",
    ):
        env_url = os.environ.get(key)
        if env_url:
            return env_url

    # Fallback to file-based SQLite in the current working directory
    db_path = Path.cwd() / ".cleveragents" / "db.sqlite"
    # Don't create directory here - let ProjectService handle it
    # SQLite requires absolute path with 4 slashes for file URLs
    return f"sqlite:///{db_path.absolute()}"


def _build_repo_indexing_service(
    database_url: str,
) -> RepoIndexingService:
    """Build a RepoIndexingService with a session factory from the database URL.

    Factory-per-resolution matches the existing project pattern (see
    _build_resource_registry_service, _build_namespaced_project_repo, etc.).

    Note: SQLite FK ON DELETE CASCADE requires ``PRAGMA foreign_keys=ON``
    per connection.  We rely on manual child-row deletion in persistence
    helpers instead, keeping consistency with the rest of the codebase.
    """
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker

    engine = create_engine(database_url, echo=False)
    factory = sessionmaker(bind=engine, expire_on_commit=False)
    return RepoIndexingService(session_factory=factory)


def _build_session_factory(database_url: str) -> sessionmaker[Session]:
    """Build a SQLAlchemy session factory from a database URL.

    Returns a ``sessionmaker`` instance (callable) that the CLI
    ``project context`` commands use to obtain short-lived sessions.
    """
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker

    engine = create_engine(database_url, echo=False)
    return sessionmaker(bind=engine, expire_on_commit=False)


def _build_resource_registry_service(
    database_url: str,
) -> ResourceRegistryService:
    """Build a ResourceRegistryService with a session factory from the database URL."""
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker

    engine = create_engine(database_url, echo=False)
    factory = sessionmaker(bind=engine, expire_on_commit=False)
    return ResourceRegistryService(session_factory=factory)


def _build_namespaced_project_repo(
    database_url: str,
) -> NamespacedProjectRepository:
    """Build a NamespacedProjectRepository with a session factory."""
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker

    engine = create_engine(database_url, echo=False)
    factory = sessionmaker(bind=engine, expire_on_commit=False)
    return NamespacedProjectRepository(session_factory=factory)


def _build_project_resource_link_repo(
    database_url: str,
) -> ProjectResourceLinkRepository:
    """Build a ProjectResourceLinkRepository with a session factory."""
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker

    engine = create_engine(database_url, echo=False)
    factory = sessionmaker(bind=engine, expire_on_commit=False)
    return ProjectResourceLinkRepository(session_factory=factory)


def _build_checkpoint_service(
    database_url: str,
    plan_lifecycle_service: PlanLifecycleService | None = None,
) -> CheckpointService:
    """Build a CheckpointService backed by a database repository.

    When *plan_lifecycle_service* is provided, rollback guard checks
    query persistent plan state (``processing_state``, ``sandbox_refs``)
    instead of relying on in-memory flags.
    """
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker

    engine = create_engine(database_url, echo=False)
    factory = sessionmaker(bind=engine, expire_on_commit=False)
    repository = CheckpointRepository(session_factory=factory)
    return CheckpointService(
        repository=repository,
        plan_lifecycle_service=plan_lifecycle_service,
    )


def _resolve_auto_reindex() -> bool:
    """Read ``index.auto-reindex`` from ConfigService, defaulting to True."""
    try:
        from cleveragents.application.services.config_service import (
            ConfigService,
        )

        svc = ConfigService()
        resolved = svc.resolve("index.auto-reindex")
        return bool(resolved.value) if resolved.value is not None else True
    except Exception:
        return True


def _build_analyzer_registry() -> AnalyzerRegistry:
    """Build an AnalyzerRegistry with built-in analyzers registered."""
    from cleveragents.domain.models.acms.python_analyzer import PythonAnalyzer

    registry = AnalyzerRegistry()
    registry.register(PythonAnalyzer(), priority=10)
    return registry


def _build_resource_file_watcher(
    event_bus: ReactiveEventBus,
) -> ResourceFileWatcher:
    """Build a ResourceFileWatcher with ConfigService integration."""
    auto_reindex = _resolve_auto_reindex()
    return ResourceFileWatcher(
        event_bus=event_bus,
        auto_reindex=auto_reindex,
    )


def _build_trace_service(
    database_url: str,
    settings: Settings | None = None,
) -> TraceService:
    """Build a TraceService backed by a database LLMTraceRepository."""
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker

    engine = create_engine(database_url, echo=False)
    factory = sessionmaker(bind=engine, expire_on_commit=False)
    repository = LLMTraceRepository(session_factory=factory)
    resolved_settings = settings or get_settings()
    return TraceService(settings=resolved_settings, repository=repository)


def _build_skill_service(
    database_url: str,
) -> SkillService:
    """Build a SkillService with DB persistence.

    Follows the same ``_build_*`` pattern used by ``_build_session_service``
    and other DB-backed services: create engine, session factory, repository,
    then inject into the service constructor.

    Falls back to an in-memory ``SkillService()`` when the database is
    unavailable (e.g. before ``agents init``).  Only expected operational
    exceptions (``ImportError``, ``OperationalError``, ``DatabaseError``,
    ``OSError``) are caught — unexpected errors propagate to the caller.
    """
    try:
        from sqlalchemy import create_engine
        from sqlalchemy.orm import sessionmaker
    except ImportError as exc:
        _logger.warning(
            "skill_service_db_fallback",
            reason="SQLAlchemy not available, falling back to in-memory SkillService",
            exc_info=True,
            error_type=type(exc).__name__,
        )
        return SkillService()

    from sqlalchemy import inspect as sa_inspect
    from sqlalchemy.exc import DatabaseError, OperationalError

    try:
        from cleveragents.infrastructure.database.models import (
            Base,
            SkillItemModel,
            SkillModel,
        )
        from cleveragents.infrastructure.database.repositories import (
            SkillRepository,
        )

        engine = create_engine(database_url, echo=False)

        # Targeted table creation — only skill tables, never the full
        # schema.  Mirrors the pattern in _build_session_service where
        # session tables are created if missing.  Without this, the
        # SkillRepository fails silently on every query because the
        # skills and skill_items tables do not exist in databases
        # initialised before the skill registry migration was added.
        # See bug #980.
        inspector = sa_inspect(engine)
        existing_tables = set(inspector.get_table_names())
        tables_to_create = [
            model.__table__
            for model in (SkillModel, SkillItemModel)
            if model.__tablename__ not in existing_tables
        ]
        if tables_to_create:
            Base.metadata.create_all(engine, tables=tables_to_create)

        factory = sessionmaker(bind=engine, expire_on_commit=False)
        skill_repo = SkillRepository(session_factory=factory, auto_commit=True)
        return SkillService(
            skill_repo=skill_repo,
            session_factory=factory,
        )
    except (OperationalError, DatabaseError, OSError) as exc:
        _logger.warning(
            "skill_service_db_fallback",
            reason="Failed to create DB-backed SkillService, falling back to in-memory",
            exc_info=True,
            error_type=type(exc).__name__,
        )
        return SkillService()


def _build_automation_profile_service(
    database_url: str,
) -> AutomationProfileService:
    """Build an ``AutomationProfileService`` with DB-backed persistence."""
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker

    from cleveragents.infrastructure.database.repositories import (
        AutomationProfileRepository,
    )

    engine = create_engine(database_url, echo=False)
    factory = sessionmaker(bind=engine, expire_on_commit=False)
    profile_repo = AutomationProfileRepository(
        session_factory=factory,
        auto_commit=True,
    )
    return AutomationProfileService(repo=profile_repo)


def _build_tool_registry_service(
    database_url: str,
) -> ToolRegistryService:
    """Build a ToolRegistryService with DB-backed persistence.

    Follows the same _build_* pattern used by _build_skill_service
    and other DB-backed services: create engine, session factory, repositories,
    then inject into the service constructor.

    This builder is registered in the DI container so that callers can
    resolve ToolRegistryService via container.tool_registry_service()
    without manually wiring the dependency graph (Forgejo #3006).
    """
    from sqlalchemy import create_engine
    from sqlalchemy.orm import sessionmaker

    from cleveragents.infrastructure.database.repositories import (
        ToolRegistryRepository,
        ValidationAttachmentRepository,
    )

    engine = create_engine(database_url, echo=False)
    factory = sessionmaker(bind=engine, expire_on_commit=False)
    tool_repo = ToolRegistryRepository(session_factory=factory)
    attachment_repo = ValidationAttachmentRepository(session_factory=factory)
    return ToolRegistryService(
        tool_repo=tool_repo,
        attachment_repo=attachment_repo,
    )


def _build_session_service(
    database_url: str,
    event_bus: ReactiveEventBus | None = None,
) -> PersistentSessionService:
    """Build a PersistentSessionService with auto-committing repositories.

    The CLI operates outside a ``UnitOfWork``, so repositories are
    constructed with ``auto_commit=True`` to ensure each operation
    commits and closes its own database session.

    Session and session-message tables are created if they do not yet
    exist.  Only the two session-related tables are touched — this
    avoids the broad ``Base.metadata.create_all()`` that would bypass
    Alembic for every other model (review finding M3/M8).
    """
    from sqlalchemy import create_engine
    from sqlalchemy import inspect as sa_inspect
    from sqlalchemy.orm import sessionmaker

    from cleveragents.infrastructure.database.models import (
        Base,
        SessionMessageModel,
        SessionModel,
    )

    engine = create_engine(database_url, echo=False)

    # Targeted table creation — only session tables, never the full schema.
    inspector = sa_inspect(engine)
    existing_tables = set(inspector.get_table_names())
    tables_to_create = [
        model.__table__
        for model in (SessionModel, SessionMessageModel)
        if model.__tablename__ not in existing_tables
    ]
    if tables_to_create:
        Base.metadata.create_all(engine, tables=tables_to_create)

    factory = sessionmaker(bind=engine, expire_on_commit=False)
    session_repo = SessionRepository(session_factory=factory, auto_commit=True)
    message_repo = SessionMessageRepository(session_factory=factory, auto_commit=True)
    return PersistentSessionService(session_repo, message_repo, event_bus=event_bus)


class Container(containers.DeclarativeContainer):
    """Dependency injection container using dependency-injector.

    Manages service lifetimes and dependencies according to ADR-003.
    """

    # Configuration
    config = providers.Configuration()

    # Settings - Singleton
    settings = providers.Singleton(
        get_settings,
    )

    provider_registry = providers.Singleton(
        get_provider_registry,
        settings=settings,
    )

    # Database URL - callable that returns proper path
    database_url = providers.Callable(get_database_url)

    # Session factory - builds a sessionmaker bound to the database URL.
    # Used by CLI project-context commands for short-lived sessions.
    # Singleton chosen intentionally — avoids creating duplicate engines
    # per call.  The DB URL is stable for the process lifetime; if
    # dynamic URL refresh is ever needed, switch to Factory and add
    # engine disposal logic.
    session_factory = providers.Singleton(
        _build_session_factory,
        database_url=database_url,
    )

    # Unit of Work - Factory (new instance per request)
    unit_of_work = providers.Factory(
        UnitOfWork,
        database_url=database_url,
    )
    config_service = providers.Singleton(ConfigService)

    # AI Provider - Singleton that builds either a mock or real provider
    ai_provider: providers.Provider[AIProviderInterface | None] = providers.Singleton(
        get_ai_provider,
        settings=settings,
        provider_registry=provider_registry,
    )

    # Event Bus - Singleton (one shared instance for the process)
    event_bus = providers.Singleton(ReactiveEventBus)

    # Services - Factory (new instance per request with injected dependencies)
    project_service = providers.Factory(
        ProjectService,
        settings=settings,
        unit_of_work=unit_of_work,
        event_bus=event_bus,
    )

    vector_store_service = providers.Factory(
        VectorStoreService,
        settings=settings,
        unit_of_work=unit_of_work,
        config_service=config_service,
    )
    acms_vector_store_service = providers.Singleton(
        VectorStoreService,
        settings=settings,
        unit_of_work=unit_of_work,
        config_service=config_service,
    )

    context_service = providers.Factory(
        ContextService,
        settings=settings,
        unit_of_work=unit_of_work,
        vector_store_service=vector_store_service,
    )

    actor_service = providers.Factory(
        ActorService,
        settings=settings,
        unit_of_work=unit_of_work,
        event_bus=event_bus,
    )

    actor_registry = providers.Factory(
        ActorRegistry,
        actor_service=actor_service,
        provider_registry=provider_registry,
        settings=settings,
    )

    plan_service = providers.Factory(
        PlanService,
        settings=settings,
        unit_of_work=unit_of_work,
        ai_provider=ai_provider,
        provider_registry=provider_registry,
        actor_service=actor_service,
    )

    # Decision Service - Factory (wraps DecisionRepository via UoW)
    decision_service = providers.Factory(
        DecisionService,
        settings=settings,
        unit_of_work=unit_of_work,
        event_bus=event_bus,
    )

    # Invariant Service - Singleton (in-memory invariant management)
    invariant_service = providers.Singleton(
        InvariantService,
    )

    # Plan Lifecycle Service - Factory (v3 four-phase lifecycle)
    plan_lifecycle_service = providers.Factory(
        PlanLifecycleService,
        settings=settings,
        unit_of_work=unit_of_work,
        decision_service=decision_service,
        event_bus=event_bus,
        invariant_service=invariant_service,
    )

    # Checkpoint Service - database-backed via CheckpointRepository
    checkpoint_service = providers.Factory(
        _build_checkpoint_service,
        database_url=database_url,
        plan_lifecycle_service=plan_lifecycle_service,
    )

    # Subplan Service - Factory (spawns child plans from decisions)
    subplan_service = providers.Factory(
        SubplanService,
        decision_service=decision_service,
    )

    # Multi-Project Service - Factory (multi-project scope management)
    multi_project_service = providers.Factory(
        MultiProjectService,
        decision_service=decision_service,
    )

    # Repo Indexing Service - indexes repository resources (#195)
    repo_indexing_service = providers.Factory(
        _build_repo_indexing_service,
        database_url=database_url,
    )

    # Resource Registry Service - uses database session factory from UoW
    resource_registry_service = providers.Factory(
        _build_resource_registry_service,
        database_url=database_url,
    )

    # Namespaced Project Repository
    namespaced_project_repo = providers.Factory(
        _build_namespaced_project_repo,
        database_url=database_url,
    )

    # Project Resource Link Repository
    project_resource_link_repo = providers.Factory(
        _build_project_resource_link_repo,
        database_url=database_url,
    )

    # Context Tier Service - Singleton so all callers share tier state
    # event_bus injected for tier transition event emission (#821)
    context_tier_service = providers.Singleton(
        ContextTierService,
        settings=settings,
        event_bus=event_bus,
    )

    # Decomposition Service - Factory (uses DecisionService + Settings)
    decomposition_service = providers.Factory(
        DecompositionService,
        decision_service=decision_service,
        settings=settings,
    )

    # Skeleton Compressor Service - stateless, Singleton is sufficient
    skeleton_compressor_service = providers.Singleton(
        SkeletonCompressorService,
    )

    # Trace Service - LLM observability (Forgejo #500)
    trace_service = providers.Factory(
        _build_trace_service,
        database_url=database_url,
        settings=settings,
    )

    # Skill Service — Singleton so skill state is consistent within the
    # same process (Forgejo #887).  The CLI ``_get_skill_service()``
    # delegates to this provider to avoid divergent instances.
    skill_service = providers.Singleton(
        _build_skill_service,
        database_url=database_url,
    )

    # Tool Registry Service — Singleton so all callers share the same
    # registry state within a process (Forgejo #3006).  The CLI
    # _get_tool_registry_service() delegates to this provider to
    # avoid manual dependency wiring.
    tool_registry_service = providers.Singleton(
        _build_tool_registry_service,
        database_url=database_url,
    )

    # Automation Profile Service - DB-backed profile CRUD/resolution
    automation_profile_service = providers.Factory(
        _build_automation_profile_service,
        database_url=database_url,
    )

    # Session Service - database-backed session management (Forgejo #554, #570, #680)
    session_service = providers.Factory(
        _build_session_service,
        database_url=database_url,
        event_bus=event_bus,
    )

    # TUI adapter bundle (presentation-layer only composition)
    tui_adapters = providers.Factory(
        TuiServiceAdapters,
        actor_service=actor_service,
        session_service=session_service,
        project_service=project_service,
        resource_registry_service=resource_registry_service,
        plan_service=plan_service,
    )

    # TUI persona wiring through DI container
    persona_registry = providers.Factory(PersonaRegistry)
    persona_state = providers.Factory(PersonaState, registry=persona_registry)

    # Metrics Emitter - structured metric emission (Forgejo #579)
    metrics_emitter = providers.Singleton(
        MetricsEmitter.from_settings,
        settings=settings,
    )

    # Cost Budget Service - Singleton (per-session & per-org budgets, #584)
    cost_budget_service = providers.Singleton(
        CostBudgetService,
        event_bus=event_bus,
    )

    # Autonomy Guardrail Service - Singleton so all callers share state
    autonomy_guardrail_service = providers.Singleton(
        AutonomyGuardrailService,
        cost_budget_service=cost_budget_service,
    )

    # Autonomy Controller — Semantic Escalation (spec § Semantic Escalation)
    autonomy_controller = providers.Singleton(
        AutonomyController,
    )

    # Execution Environment Resolver - Singleton (stateless)
    execution_environment_resolver = providers.Singleton(
        ExecutionEnvironmentResolver,
    )

    # Fix-then-Revalidate Orchestrator - Factory (per-plan lifecycle, #583)
    fix_then_revalidate_orchestrator = providers.Factory(
        FixThenRevalidateOrchestrator,
        validation_pipeline=None,
        event_bus=event_bus,
    )

    # Plugin Manager - Singleton (shared plugin lifecycle management)
    plugin_manager = providers.Singleton(PluginManager)

    # ACMS Backend Abstraction Layer — configurable via provider selection.
    # Default: config-driven selection with graceful fallback to
    # in-memory stubs when production backends are disabled or missing.
    text_backend = providers.Singleton(InMemoryTextBackend)
    vector_backend = providers.Singleton(
        build_vector_backend,
        vector_store_service=acms_vector_store_service,
    )
    graph_backend = providers.Singleton(InMemoryGraphBackend)

    # ACMS UKO Indexer — write-side index backends (#578)
    analyzer_registry = providers.Singleton(_build_analyzer_registry)
    index_text_backend = providers.Singleton(InMemoryTextIndexBackend)
    index_vector_backend = providers.Singleton(
        build_vector_index_backend,
        vector_store_service=acms_vector_store_service,
    )
    index_graph_backend = providers.Singleton(InMemoryGraphIndexBackend)
    uko_indexer = providers.Singleton(
        UKOIndexer,
        analyzer_registry=analyzer_registry,
        graph_backend=index_graph_backend,
        text_backend=index_text_backend,
        vector_backend=index_vector_backend,
    )

    # ACMS Pipeline — context assembly pipeline (Forgejo #873).
    # Singleton ensures consistent strategy registry across callers.
    acms_pipeline = providers.Singleton(
        ACMSPipeline,
        settings=settings,
        unit_of_work=unit_of_work,
        plugin_manager=plugin_manager,
    )

    # ACMS ResourceFileWatcher — watches resource files for changes (#578).
    # Reads ``index.auto-reindex`` from ConfigService at construction.
    resource_file_watcher = providers.Singleton(
        _build_resource_file_watcher,
        event_bus=event_bus,
    )

    # Audit Service - Singleton (one shared audit store per process).
    # N6: Pass database_url from the container so audit entries are
    # written to the same database as all other services, not to the
    # Settings default ("sqlite:///cleveragents.db").
    audit_service = providers.Singleton(
        AuditService,
        settings=settings,
        database_url=database_url,
    )

    # Audit Event Subscriber - Singleton (wires EventBus -> AuditService)
    audit_event_subscriber = providers.Singleton(
        AuditEventSubscriber,
        audit_service=audit_service,
        event_bus=event_bus,
    )

    # Correction Service - registered in the DI container so that
    # event_bus is always wired and CORRECTION_APPLIED audit events
    # are emitted in production (N9).
    correction_service = providers.Singleton(
        CorrectionService,
        checkpoint_service=checkpoint_service,
        event_bus=event_bus,
    )

    # Reactive routing — lazy-imported to avoid pulling in langchain /
    # langgraph / rx at container-definition time, which adds ~2-4 s of
    # import overhead to every CLI subprocess.
    stream_router = providers.Singleton(_build_stream_router)
    langgraph_bridge = providers.Singleton(
        _build_langgraph_bridge, stream_router=stream_router
    )
    route_bridge = providers.Singleton(
        _build_route_bridge, stream_router=stream_router, agents=actor_registry
    )

    # After container creation, wire actors into reactive router/bridge
    reactive_bootstrap = providers.Callable(
        _lazy_register_registry_agents,
        stream_router=stream_router,
        route_bridge=route_bridge,
        actor_registry=actor_registry,
    )


# Global container instance
_container: Container | None = None
_audit_subscriber_initialized: bool = False


def get_container() -> Container:
    """Get the global container instance.

    Creates container on first call, returns same instance thereafter.
    Eagerly instantiates the :class:`AuditEventSubscriber` singleton so
    that EventBus subscriptions are registered at startup and
    security-relevant events are automatically routed to the audit log.

    If audit subscriber initialization fails (e.g., database not yet
    initialized), it is retried on every subsequent call until it
    succeeds.  This ensures that long-lived server processes eventually
    register audit subscriptions once prerequisites become available
    (bug #992).
    """
    global _container, _audit_subscriber_initialized
    if _container is None:
        # Configure structlog before creating the container so that
        # plugin_manager extension-point registration debug messages go to
        # the Python logging infrastructure (respecting the WARNING threshold)
        # rather than structlog's default PrintLoggerFactory which writes to
        # stdout and pollutes machine-readable CLI output formats.
        from cleveragents.config.logging import configure_structlog

        configure_structlog(log_level="WARNING")
        _container = Container()
    # Retry audit subscriber initialization on every call until it
    # succeeds.  Previously, a failed attempt during the first
    # get_container() call was never retried, leaving audit
    # subscriptions permanently unregistered in long-lived processes
    # (bug #992).
    if not _audit_subscriber_initialized:
        # Don't initialize database here - let services handle it
        # Eagerly wire the audit subscriber so EventBus subscriptions
        # are active from startup (lazy singletons are only created on
        # first access; without this call the subscriber would never
        # register its handlers).
        try:
            _container.audit_event_subscriber()
            _audit_subscriber_initialized = True
        except Exception as exc:
            _logger.warning(
                "audit_subscriber_deferred",
                reason="Database not yet initialised; audit subscriptions "
                "will be retried on next get_container() call.",
                error_type=type(exc).__name__,
                error_message=redact_value(str(exc)),
            )
        # Register all 30 spec-defined extension points on the
        # PluginManager singleton so plugins can be discovered and
        # validated against typed Protocol interfaces (#939).
        try:
            pm = _container.plugin_manager()
            register_all_extension_points(pm)
        except Exception as exc:
            _logger.warning(
                "extension_points_deferred",
                reason="Failed to register extension points at startup.",
                error_type=type(exc).__name__,
                error_message=redact_value(str(exc)),
            )
    return _container


def reset_container() -> None:
    """Reset the global container.

    Useful for testing to ensure clean state between tests.
    """
    global _container, _audit_subscriber_initialized
    _container = None
    _audit_subscriber_initialized = False


def override_providers(**overrides: object) -> None:
    """Override providers for testing.

    Example:
        override_providers(
            database_url="sqlite:///:memory:",
            settings=mock_settings,
        )

    Args:
        **overrides: Provider overrides as keyword arguments
    """
    container = get_container()
    for name, value in overrides.items():
        if hasattr(container, name):
            provider = getattr(container, name)
            # Special handling for database_url which is a Callable
            if name == "database_url":
                # Override with a simple Object provider returning the value
                provider.override(providers.Object(value))
            else:
                provider.override(providers.Object(value))
