"""Dependency Injection container — wires everything together at startup.

Composition Root pattern: all wiring happens here, nowhere else.
Modules depend on interfaces, container provides implementations.
"""

from __future__ import annotations

import logging
from collections.abc import Callable
from uuid import UUID

from rigovo.config import AppConfig
from rigovo.domain.interfaces.domain_plugin import DomainPlugin
from rigovo.domain.interfaces.event_emitter import EventEmitter
from rigovo.domain.interfaces.llm_provider import LLMProvider
from rigovo.domain.interfaces.quality_gate import QualityGate
from rigovo.domain.services.cost_calculator import CostCalculator, ModelPricing
from rigovo.domain.services.memory_ranker import MemoryRanker
from rigovo.domain.services.team_assembler import TeamAssemblerService
from rigovo.domains.engineering import EngineeringDomain
from rigovo.infrastructure.llm.llm_factory import LLMProviderFactory
from rigovo.infrastructure.persistence.sqlite_memory_repo import SqliteMemoryRepository
from rigovo.infrastructure.quality.gate_builder import QualityGateBuilder

logger = logging.getLogger(__name__)


class Container:
    """
    Application root — creates and holds all dependencies.

    Delegates creation to focused factories (LLMProviderFactory,
    QualityGateBuilder) and lazily initialises infrastructure.
    """

    def __init__(self, config: AppConfig) -> None:
        self.config = config

        # Domain Services (pure, no infra deps)
        self.model_pricing = ModelPricing()
        self.cost_calculator = CostCalculator(self.model_pricing)
        self.team_assembler = TeamAssemblerService()
        self.memory_ranker = MemoryRanker()

        # Domain Plugins
        self.domains: dict[str, DomainPlugin] = {
            "engineering": EngineeringDomain(),
        }

        # Factories — LLM factory is rebuilt lazily once DB is available
        self._llm_factory: LLMProviderFactory | None = None
        self._gate_builder = QualityGateBuilder(config, self.domains)

        # Lazy infrastructure
        self._quality_gates: list[QualityGate] = []
        self._db = None
        self._settings_repo = None
        self._event_emitter: EventEmitter | None = None
        self._sync_client = None
        self._plugin_registry = None

    def get_db(self):
        """Get or create the local SQLite database."""
        if self._db is None:
            backend = str(self.config.db_backend).strip().lower()

            if backend == "postgres":
                from rigovo.infrastructure.persistence.postgres_local import (
                    PostgresDatabase,
                )

                self._db = PostgresDatabase(self.config.db_url)
            else:
                from rigovo.infrastructure.persistence.sqlite_local import (
                    LocalDatabase,
                )

                db_path = self.config.local_db_full_path
                db_path.parent.mkdir(parents=True, exist_ok=True)
                self._db = LocalDatabase(str(db_path))
        return self._db

    def get_settings_repo(self):
        """Get or create the encrypted settings repository."""
        if self._settings_repo is None:
            from rigovo.infrastructure.persistence.sqlite_settings_repo import (
                SqliteSettingsRepository,
            )

            self._settings_repo = SqliteSettingsRepository(self.get_db())
        return self._settings_repo

    def _get_llm_factory(self) -> LLMProviderFactory:
        """Get or create the LLM factory with live key resolution."""
        if self._llm_factory is None:
            settings = self.get_settings_repo()
            self._llm_factory = LLMProviderFactory(
                config=self.config.llm,
                key_resolver=settings.get,  # reads from encrypted SQLite
            )
        return self._llm_factory

    def get_event_emitter(self) -> EventEmitter:
        """Get or create the in-process event emitter."""
        if self._event_emitter is None:
            from rigovo.infrastructure.events import InProcessEventEmitter

            self._event_emitter = InProcessEventEmitter()
        return self._event_emitter

    def get_domain(self, domain_id: str) -> DomainPlugin:
        """Get a domain plugin by ID."""
        if domain_id not in self.domains:
            raise ValueError(f"Unknown domain: {domain_id}. Available: {list(self.domains.keys())}")
        return self.domains[domain_id]

    def get_llm(self, model: str | None = None) -> LLMProvider:
        """Get an LLM provider for a given model."""
        return self._get_llm_factory().get(model)

    def get_master_llm(self) -> LLMProvider:
        """Get the LLM provider used by the Master Agent."""
        return self._get_llm_factory().get(self.config.llm.model)

    def llm_factory(self, model: str) -> LLMProvider:
        """Factory function for creating LLM providers (passed to graph)."""
        return self._get_llm_factory().get(model)

    def get_quality_gates(self) -> list[QualityGate]:
        """Get configured quality gates."""
        if not self._quality_gates:
            self._quality_gates = self._gate_builder.build()
        return self._quality_gates

    def get_sync_client(self):
        """Get or create the cloud sync client."""
        if self._sync_client is None:
            from rigovo.infrastructure.cloud.sync_client import (
                CloudSyncClient,
            )

            workspace_id = None
            if self.config.workspace_id:
                try:
                    workspace_id = UUID(self.config.workspace_id)
                except ValueError:
                    pass
            self._sync_client = CloudSyncClient(
                api_url=self.config.cloud.api_url,
                api_key=self.config.cloud.api_key,
                workspace_id=workspace_id,
            )
        return self._sync_client

    def get_embedding_provider(self):
        """Get the local embedding provider."""
        from rigovo.infrastructure.embeddings.local_embeddings import (
            LocalEmbeddingProvider,
        )

        return LocalEmbeddingProvider()

    def build_run_task_command(
        self,
        offline: bool = False,
        approval_handler: Callable | None = None,
        enable_streaming: bool = True,
        enable_parallel: bool = True,
        auto_approve: bool = True,
        ci_mode: bool = False,
    ):
        """Build a fully-wired RunTaskCommand."""
        from rigovo.application.commands.run_task import RunTaskCommand

        workspace_id = UUID(self.config.workspace_id) if self.config.workspace_id else UUID(int=0)

        return RunTaskCommand(
            workspace_id=workspace_id,
            project_root=self.config.project_root,
            master_llm=self.get_master_llm(),
            llm_factory=self.llm_factory,
            cost_calculator=self.cost_calculator,
            team_assembler=self.team_assembler,
            quality_gates=self.get_quality_gates(),
            domain_plugins=self.domains,
            event_emitter=self.get_event_emitter(),
            db=self.get_db(),
            approval_handler=approval_handler,
            max_retries=self.config.max_retries,
            team_configs=self.config.yml.teams,
            consultation_policy=self.config.yml.orchestration.consultation.model_dump(),
            subagent_policy=self.config.yml.orchestration.subagents.model_dump(),
            deep_mode=self.config.yml.orchestration.deep_mode,
            deep_pro=self.config.yml.orchestration.deep_pro,
            replan_policy=self.config.yml.orchestration.replan.model_dump(),
            memory_repo=SqliteMemoryRepository(self.get_db()),
            embedding_provider=self.get_embedding_provider(),
            plugin_registry=self.get_plugin_registry() if self.config.yml.plugins.enabled else None,
            integration_policy={
                "enable_connector_tools": self.config.yml.plugins.enable_connector_tools,
                "enable_mcp_tools": self.config.yml.plugins.enable_mcp_tools,
                "enable_action_tools": self.config.yml.plugins.enable_action_tools,
                "min_trust_level": self.config.yml.plugins.min_trust_level,
                "allowed_plugin_ids": list(self.config.yml.plugins.allowed_plugin_ids),
                "allowed_connector_operations": list(
                    self.config.yml.plugins.allowed_connector_operations
                ),
                "allowed_mcp_operations": list(self.config.yml.plugins.allowed_mcp_operations),
                "allowed_action_operations": list(
                    self.config.yml.plugins.allowed_action_operations
                ),
                "allow_approval_required_actions": bool(
                    self.config.yml.plugins.allow_approval_required_actions
                ),
                "allow_sensitive_payload_keys": bool(
                    self.config.yml.plugins.allow_sensitive_payload_keys
                ),
                "allowed_shell_commands": list(self.config.yml.plugins.allowed_shell_commands),
                "dry_run": self.config.yml.plugins.dry_run,
            },
            ci_mode=ci_mode,
            offline=offline,
            enable_streaming=enable_streaming,
            enable_parallel=enable_parallel,
            auto_approve=auto_approve,
        )

    def get_plugin_registry(self):
        """Get or create the local plugin registry."""
        if self._plugin_registry is None:
            from rigovo.infrastructure.plugins.loader import PluginRegistry

            self._plugin_registry = PluginRegistry(
                project_root=self.config.project_root,
                plugin_paths=self.config.yml.plugins.paths,
                enabled_plugins=self.config.yml.plugins.enabled_plugins,
            )
        return self._plugin_registry

    def close(self) -> None:
        """Clean up resources."""
        if self._db:
            self._db.close()
