"""
Dependency injection container for the test runner.

Uses ``dependency-injector`` to wire all services.  The container is created
once at application startup (CLI entry point) and threaded through to the
runners.

Usage::

    container = TestRunnerContainer()
    container.config.from_dict(load_config(project_root).__dict__)
    capture_runner = container.capture_runner()
"""

from __future__ import annotations

from dependency_injector import containers, providers

from .config import TestRunnerConfig, load_config
from .database_dialect import DatabaseDialect
from .database_executor import DatabaseExecutorFactory


# ---------------------------------------------------------------------------
# Factory provider helpers
# ---------------------------------------------------------------------------

def _create_sqlserver_factory() -> DatabaseExecutorFactory:
    from test_runner.capture.sources.sqlserver import SqlServerExecutorFactory
    return SqlServerExecutorFactory()


def _create_oracle_factory() -> DatabaseExecutorFactory:
    from test_runner.capture.sources.oracle import OracleExecutorFactory
    return OracleExecutorFactory()


def _create_redshift_factory() -> DatabaseExecutorFactory:
    from test_runner.capture.sources.redshift import RedshiftExecutorFactory
    return RedshiftExecutorFactory()


def _create_teradata_factory() -> DatabaseExecutorFactory:
    from test_runner.capture.sources.teradata import TeradataExecutorFactory
    return TeradataExecutorFactory()


def _create_snowflake_factory() -> DatabaseExecutorFactory:
    from test_runner.validate.targets.snowflake import SnowflakeExecutorFactory
    return SnowflakeExecutorFactory()


def _build_registry(
    sqlserver: DatabaseExecutorFactory,
    oracle: DatabaseExecutorFactory,
    redshift: DatabaseExecutorFactory,
    teradata: DatabaseExecutorFactory,
    snowflake: DatabaseExecutorFactory,
) -> dict[DatabaseDialect, DatabaseExecutorFactory]:
    """Build the dialect -> factory mapping."""
    registry: dict[DatabaseDialect, DatabaseExecutorFactory] = {}
    for factory in [sqlserver, oracle, redshift, teradata, snowflake]:
        registry[factory.dialect] = factory
    return registry


# ---------------------------------------------------------------------------
# Container
# ---------------------------------------------------------------------------

class TestRunnerContainer(containers.DeclarativeContainer):
    """Root DI container for the test runner application."""

    # -- configuration -------------------------------------------------------
    config = providers.Singleton(TestRunnerConfig)

    # -- executor factories (one per dialect) --------------------------------
    sqlserver_factory = providers.Singleton(_create_sqlserver_factory)
    oracle_factory = providers.Singleton(_create_oracle_factory)
    redshift_factory = providers.Singleton(_create_redshift_factory)
    teradata_factory = providers.Singleton(_create_teradata_factory)
    snowflake_factory = providers.Singleton(_create_snowflake_factory)

    # -- registry (dialect -> factory mapping) -------------------------------
    factory_registry = providers.Singleton(
        _build_registry,
        sqlserver=sqlserver_factory,
        oracle=oracle_factory,
        redshift=redshift_factory,
        teradata=teradata_factory,
        snowflake=snowflake_factory,
    )


def create_container(
    project_root: str | None = None,
    *,
    source_connection: str | None = None,
    target_connection: str | None = None,
) -> TestRunnerContainer:
    """Create and configure a container, optionally loading config from disk.

    Args:
        project_root: If provided, loads ``settings/test_config.yaml`` and
            sets the config provider.  If ``None``, the config provider
            returns a default ``TestRunnerConfig``.
        source_connection: Optional named source connection (resolved from
            ``~/.snowflake/snowct/<dialect>.toml``). Falls back to the
            TOML's ``default_connection_name`` when ``None``.
        target_connection: Optional named Snowflake target connection
            (resolved from ``~/.snowflake/config.toml``). Falls back to
            the TOML's ``default_connection_name`` when ``None``.

    Returns:
        A fully configured :class:`TestRunnerContainer`.
    """
    container = TestRunnerContainer()

    if project_root is not None:
        cfg = load_config(
            project_root,
            source_connection=source_connection,
            target_connection=target_connection,
        )
        container.config.override(providers.Object(cfg))

    return container
