import inspect
import typing
from collections.abc import Awaitable, Callable, Generator
from contextlib import AbstractAsyncContextManager, contextmanager
from types import TracebackType
from typing import Final, Self, cast, final, overload, override

from fastapi import FastAPI

from wirio._integrations._fastapi_dependency_injection import (
    FastApiDependencyInjection,
)
from wirio._service_lookup._typed_type import TypedType
from wirio._wirio_undefined import WirioUndefined
from wirio.abstractions.service_scope import ServiceScope
from wirio.base_service_container import BaseServiceContainer
from wirio.exceptions import (
    NoKeyedSingletonServiceRegisteredError,
    NoSingletonServiceRegisteredError,
    ServiceContainerAlreadyBuiltError,
    ServiceContainerNotBuiltError,
)
from wirio.service_descriptor import ServiceDescriptor
from wirio.service_lifetime import ServiceLifetime
from wirio.service_provider import ServiceProvider


@final
class ServiceContainer(
    BaseServiceContainer, AbstractAsyncContextManager["ServiceContainer"]
):
    """Container to register and resolve services."""

    _descriptors: Final[list[ServiceDescriptor]]
    _service_provider: ServiceProvider | None = None

    def __init__(self) -> None:
        self._descriptors = []
        self._service_provider = None

    @property
    def descriptors(self) -> list[ServiceDescriptor]:
        return self._descriptors

    @override
    async def get_object(self, service_type: TypedType) -> object | None:
        service_provider = await self._get_service_provider()
        return await service_provider.get_object(service_type)

    @override
    async def get_keyed_object(
        self, service_key: object | None, service_type: TypedType
    ) -> object | None:
        service_provider = await self._get_service_provider()
        return await service_provider.get_keyed_object(
            service_key=service_key, service_type=service_type
        )

    @override
    async def get[TService](self, service_type: type[TService]) -> TService:
        service_provider = await self._get_service_provider()
        return await service_provider.get(service_type)

    @override
    async def try_get[TService](self, service_type: type[TService]) -> TService | None:
        service_provider = await self._get_service_provider()
        return await service_provider.try_get(service_type)

    @override
    async def get_keyed[TService](
        self, service_key: object | None, service_type: type[TService]
    ) -> TService:
        service_provider = await self._get_service_provider()
        return await service_provider.get_keyed(
            service_key=service_key, service_type=service_type
        )

    @override
    async def try_get_keyed[TService](
        self, service_key: object | None, service_type: type[TService]
    ) -> TService | None:
        service_provider = await self._get_service_provider()
        return await service_provider.try_get_keyed(
            service_key=service_key, service_type=service_type
        )

    @override
    def create_scope(self) -> ServiceScope:
        self._ensure_service_container_is_built()
        assert self._service_provider is not None
        return self._service_provider.create_scope()

    async def close(self) -> None:
        if self._service_provider is not None:
            await self._service_provider.__aexit__(None, None, None)

        self._service_provider = None

    @contextmanager
    def override(
        self, service_type: type, implementation_instance: object | None
    ) -> Generator[None]:
        """Override a service registration within the context manager scope.

        It can be used to temporarily replace a service for testing specific scenarios. Don't use it in production.
        """
        self._ensure_service_container_is_built()
        assert self._service_provider is not None

        with self._service_provider.override(
            service_type=service_type,
            implementation_instance=implementation_instance,
        ):
            yield

    @contextmanager
    def override_keyed(
        self,
        service_key: object | None,
        service_type: type,
        implementation_instance: object | None,
    ) -> Generator[None]:
        """Override a keyed service registration within the context manager scope.

        It can be used to temporarily replace a service for testing specific scenarios. Don't use it in production.
        """
        self._ensure_service_container_is_built()
        assert self._service_provider is not None

        with self._service_provider.override_keyed(
            service_key=service_key,
            service_type=service_type,
            implementation_instance=implementation_instance,
        ):
            yield

    async def _get_service_provider(self) -> ServiceProvider:
        if self._service_provider is None:
            self._service_provider = self._build_service_provider()
            await self._service_provider.__aenter__()

        return self._service_provider

    def _build_service_provider(self) -> ServiceProvider:
        """Create a :class:`ServiceProvider` containing services from the provided :class:`ServiceContainer`."""
        self._service_provider = ServiceProvider(self._descriptors)
        return self._service_provider

    @overload
    def add_transient[TService](self, service_type: type[TService], /) -> None: ...

    @overload
    def add_transient[TService](
        self,
        service_type: type[TService],
        implementation_factory: Callable[..., Awaitable[TService]],
        /,
    ) -> None: ...

    @overload
    def add_transient[TService](
        self,
        service_type: type[TService],
        implementation_factory: Callable[..., TService],
        /,
    ) -> None: ...

    @overload
    def add_transient[TService](
        self,
        implementation_factory: Callable[..., Awaitable[TService]],
        /,
    ) -> None: ...

    @overload
    def add_transient[TService](
        self,
        implementation_factory: Callable[..., TService],
        /,
    ) -> None: ...

    @overload
    def add_transient[TService](
        self,
        service_type: type[TService],
        implementation_type: type,
        /,
    ) -> None: ...

    def add_transient[TService](
        self,
        service_type_or_implementation_factory: type[TService]
        | Callable[..., Awaitable[TService]]
        | Callable[..., TService],
        implementation_factory_or_implementation_type_or_none: Callable[
            ..., Awaitable[TService]
        ]
        | Callable[..., TService]
        | type
        | None = None,
        /,
    ) -> None:
        """Add a transient service."""
        self._add_from_overloaded_constructor(
            lifetime=ServiceLifetime.TRANSIENT,
            service_type_or_implementation_factory=service_type_or_implementation_factory,
            implementation_factory_or_implementation_type_or_implementation_instance_or_none=implementation_factory_or_implementation_type_or_none,
        )

    @overload
    def add_singleton[TService](self, service_type: type[TService], /) -> None: ...

    @overload
    def add_singleton[TService](
        self,
        service_type: type[TService],
        implementation_factory: Callable[..., Awaitable[TService]],
        /,
    ) -> None: ...

    @overload
    def add_singleton[TService](
        self,
        service_type: type[TService],
        implementation_factory: Callable[..., TService],
        /,
    ) -> None: ...

    @overload
    def add_singleton[TService](
        self,
        implementation_factory: Callable[..., Awaitable[TService]],
        /,
    ) -> None: ...

    @overload
    def add_singleton[TService](
        self,
        implementation_factory: Callable[..., TService],
        /,
    ) -> None: ...

    @overload
    def add_singleton[TService](
        self,
        service_type: type[TService],
        implementation_type: type,
        /,
    ) -> None: ...

    @overload
    def add_singleton[TService](
        self,
        service_type: type[TService],
        implementation_instance: object,
        /,
    ) -> None: ...

    def add_singleton[TService](
        self,
        service_type_or_implementation_factory: type[TService]
        | Callable[..., Awaitable[TService]]
        | Callable[..., TService],
        implementation_factory_or_implementation_type_or_implementation_instance_or_none: Callable[
            ..., Awaitable[TService]
        ]
        | Callable[..., TService]
        | type
        | object
        | None = None,
        /,
    ) -> None:
        """Add a singleton service."""
        self._add_from_overloaded_constructor(
            lifetime=ServiceLifetime.SINGLETON,
            service_type_or_implementation_factory=service_type_or_implementation_factory,
            implementation_factory_or_implementation_type_or_implementation_instance_or_none=implementation_factory_or_implementation_type_or_implementation_instance_or_none,
        )

    @overload
    def add_scoped[TService](self, service_type: type[TService], /) -> None: ...

    @overload
    def add_scoped[TService](
        self,
        service_type: type[TService],
        implementation_factory: Callable[..., Awaitable[TService]],
        /,
    ) -> None: ...

    @overload
    def add_scoped[TService](
        self,
        service_type: type[TService],
        implementation_factory: Callable[..., TService],
        /,
    ) -> None: ...

    @overload
    def add_scoped[TService](
        self,
        implementation_factory: Callable[..., Awaitable[TService]],
        /,
    ) -> None: ...

    @overload
    def add_scoped[TService](
        self,
        implementation_factory: Callable[..., TService],
        /,
    ) -> None: ...

    @overload
    def add_scoped[TService](
        self,
        service_type: type[TService],
        implementation_type: type,
        /,
    ) -> None: ...

    def add_scoped[TService](
        self,
        service_type_or_implementation_factory: type[TService]
        | Callable[..., Awaitable[TService]]
        | Callable[..., TService],
        implementation_factory_or_implementation_type_or_none: Callable[
            ..., Awaitable[TService]
        ]
        | Callable[..., TService]
        | type
        | None = None,
        /,
    ) -> None:
        """Add a scoped service."""
        self._add_from_overloaded_constructor(
            lifetime=ServiceLifetime.SCOPED,
            service_type_or_implementation_factory=service_type_or_implementation_factory,
            implementation_factory_or_implementation_type_or_implementation_instance_or_none=implementation_factory_or_implementation_type_or_none,
        )

    @overload
    def add_keyed_transient[TKey, TService](
        self,
        service_key: TKey | None,  # pyright: ignore[reportInvalidTypeVarUse]
        service_type: type[TService],
        /,
    ) -> None: ...

    @overload
    def add_keyed_transient[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        service_type: type[TService],
        implementation_factory: Callable[
            [TKey | None, *TFactoryParameter], Awaitable[TService]
        ],
        /,
    ) -> None: ...

    @overload
    def add_keyed_transient[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        service_type: type[TService],
        implementation_factory: Callable[[TKey | None, *TFactoryParameter], TService],
        /,
    ) -> None: ...

    @overload
    def add_keyed_transient[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        implementation_factory: Callable[
            [TKey | None, *TFactoryParameter], Awaitable[TService]
        ],
        /,
    ) -> None: ...

    @overload
    def add_keyed_transient[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        implementation_factory: Callable[[TKey | None, *TFactoryParameter], TService],
        /,
    ) -> None: ...

    @overload
    def add_keyed_transient[TKey, TService](
        self,
        service_key: TKey | None,  # pyright: ignore[reportInvalidTypeVarUse]
        service_type: type[TService],
        implementation_type: type,
        /,
    ) -> None: ...

    def add_keyed_transient[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        service_type_or_implementation_factory: type[TService]
        | Callable[[TKey | None, *TFactoryParameter], Awaitable[TService]]
        | Callable[[TKey | None, *TFactoryParameter], TService],
        implementation_factory_or_implementation_type_or_none: Callable[
            [TKey | None, *TFactoryParameter], Awaitable[TService]
        ]
        | Callable[[TKey | None, *TFactoryParameter], TService]
        | type
        | None = None,
        /,
    ) -> None:
        """Add a keyed transient service."""
        self._add_from_overloaded_constructor(
            lifetime=ServiceLifetime.TRANSIENT,
            service_type_or_implementation_factory=service_type_or_implementation_factory,
            implementation_factory_or_implementation_type_or_implementation_instance_or_none=implementation_factory_or_implementation_type_or_none,
            service_key=service_key,
        )

    @overload
    def add_keyed_singleton[TKey, TService](
        self,
        service_key: TKey | None,  # pyright: ignore[reportInvalidTypeVarUse]
        service_type: type[TService],
        /,
    ) -> None: ...

    @overload
    def add_keyed_singleton[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        service_type: type[TService],
        implementation_factory: Callable[
            [TKey | None, *TFactoryParameter], Awaitable[TService]
        ],
        /,
    ) -> None: ...

    @overload
    def add_keyed_singleton[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        service_type: type[TService],
        implementation_factory: Callable[[TKey | None, *TFactoryParameter], TService],
        /,
    ) -> None: ...

    @overload
    def add_keyed_singleton[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        implementation_factory: Callable[
            [TKey | None, *TFactoryParameter], Awaitable[TService]
        ],
        /,
    ) -> None: ...

    @overload
    def add_keyed_singleton[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        implementation_factory: Callable[[TKey | None, *TFactoryParameter], TService],
        /,
    ) -> None: ...

    @overload
    def add_keyed_singleton[TKey, TService](
        self,
        service_key: TKey | None,  # pyright: ignore[reportInvalidTypeVarUse]
        service_type: type[TService],
        implementation_type: type,
        /,
    ) -> None: ...

    @overload
    def add_keyed_singleton[TKey, TService](
        self,
        service_key: TKey | None,  # pyright: ignore[reportInvalidTypeVarUse]
        service_type: type[TService],
        implementation_instance: object,
        /,
    ) -> None: ...

    def add_keyed_singleton[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        service_type_or_implementation_factory: type[TService]
        | Callable[[TKey | None, *TFactoryParameter], Awaitable[TService]]
        | Callable[[TKey | None, *TFactoryParameter], TService],
        implementation_factory_or_implementation_type_or_implementation_instance_or_none: Callable[
            [TKey | None, *TFactoryParameter], Awaitable[TService]
        ]
        | Callable[[TKey | None, *TFactoryParameter], TService]
        | type
        | object
        | None = None,
        /,
    ) -> None:
        """Add a keyed singleton service."""
        self._add_from_overloaded_constructor(
            lifetime=ServiceLifetime.SINGLETON,
            service_type_or_implementation_factory=service_type_or_implementation_factory,
            implementation_factory_or_implementation_type_or_implementation_instance_or_none=implementation_factory_or_implementation_type_or_implementation_instance_or_none,
            service_key=service_key,
        )

    @overload
    def add_keyed_scoped[TKey, TService](
        self,
        service_key: TKey | None,  # pyright: ignore[reportInvalidTypeVarUse]
        service_type: type[TService],
        /,
    ) -> None: ...

    @overload
    def add_keyed_scoped[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        service_type: type[TService],
        implementation_factory: Callable[
            [TKey | None, *TFactoryParameter], Awaitable[TService]
        ],
        /,
    ) -> None: ...

    @overload
    def add_keyed_scoped[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        service_type: type[TService],
        implementation_factory: Callable[[TKey | None, *TFactoryParameter], TService],
        /,
    ) -> None: ...

    @overload
    def add_keyed_scoped[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        implementation_factory: Callable[
            [TKey | None, *TFactoryParameter], Awaitable[TService]
        ],
        /,
    ) -> None: ...

    @overload
    def add_keyed_scoped[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        implementation_factory: Callable[[TKey | None, *TFactoryParameter], TService],
        /,
    ) -> None: ...

    @overload
    def add_keyed_scoped[TKey, TService](
        self,
        service_key: TKey | None,  # pyright: ignore[reportInvalidTypeVarUse]
        service_type: type[TService],
        implementation_type: type,
        /,
    ) -> None: ...

    def add_keyed_scoped[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        service_type_or_implementation_factory: type[TService]
        | Callable[[TKey | None, *TFactoryParameter], Awaitable[TService]]
        | Callable[[TKey | None, *TFactoryParameter], TService],
        implementation_factory_or_implementation_type_or_none: Callable[
            [TKey | None, *TFactoryParameter], Awaitable[TService]
        ]
        | Callable[..., TService]
        | type
        | None = None,
        /,
    ) -> None:
        """Add a keyed scoped service."""
        self._add_from_overloaded_constructor(
            lifetime=ServiceLifetime.SCOPED,
            service_type_or_implementation_factory=service_type_or_implementation_factory,
            implementation_factory_or_implementation_type_or_implementation_instance_or_none=implementation_factory_or_implementation_type_or_none,
            service_key=service_key,
        )

    @overload
    def add_auto_activated_singleton[TService](
        self, service_type: type[TService], /
    ) -> None: ...

    @overload
    def add_auto_activated_singleton[TService](
        self,
        service_type: type[TService],
        implementation_factory: Callable[..., Awaitable[TService]],
        /,
    ) -> None: ...

    @overload
    def add_auto_activated_singleton[TService](
        self,
        service_type: type[TService],
        implementation_factory: Callable[..., TService],
        /,
    ) -> None: ...

    @overload
    def add_auto_activated_singleton[TService](
        self,
        implementation_factory: Callable[..., Awaitable[TService]],
        /,
    ) -> None: ...

    @overload
    def add_auto_activated_singleton[TService](
        self,
        implementation_factory: Callable[..., TService],
        /,
    ) -> None: ...

    @overload
    def add_auto_activated_singleton[TService](
        self,
        service_type: type[TService],
        implementation_type: type,
        /,
    ) -> None: ...

    @overload
    def add_auto_activated_singleton[TService](
        self,
        service_type: type[TService],
        implementation_instance: object,
        /,
    ) -> None: ...

    def add_auto_activated_singleton[TService](
        self,
        service_type_or_implementation_factory: type[TService]
        | Callable[..., Awaitable[TService]]
        | Callable[..., TService],
        implementation_factory_or_implementation_type_or_implementation_instance_or_none: Callable[
            ..., Awaitable[TService]
        ]
        | Callable[..., TService]
        | type
        | object
        | None = None,
        /,
    ) -> None:
        """Add an auto-activated singleton service.

        An auto-activated singleton service is instantiated when the service provider is built (eagerly), rather than when it's first requested (lazily).
        """
        self._add_from_overloaded_constructor(
            lifetime=ServiceLifetime.SINGLETON,
            service_type_or_implementation_factory=service_type_or_implementation_factory,
            implementation_factory_or_implementation_type_or_implementation_instance_or_none=implementation_factory_or_implementation_type_or_implementation_instance_or_none,
            auto_activate=True,
        )

    @overload
    def add_auto_activated_keyed_singleton[TKey, TService](
        self,
        service_key: TKey | None,  # pyright: ignore[reportInvalidTypeVarUse]
        service_type: type[TService],
        /,
    ) -> None: ...

    @overload
    def add_auto_activated_keyed_singleton[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        service_type: type[TService],
        implementation_factory: Callable[
            [TKey | None, *TFactoryParameter], Awaitable[TService]
        ],
        /,
    ) -> None: ...

    @overload
    def add_auto_activated_keyed_singleton[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        service_type: type[TService],
        implementation_factory: Callable[[TKey | None, *TFactoryParameter], TService],
        /,
    ) -> None: ...

    @overload
    def add_auto_activated_keyed_singleton[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        implementation_factory: Callable[
            [TKey | None, *TFactoryParameter], Awaitable[TService]
        ],
        /,
    ) -> None: ...

    @overload
    def add_auto_activated_keyed_singleton[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        implementation_factory: Callable[[TKey | None, *TFactoryParameter], TService],
        /,
    ) -> None: ...

    @overload
    def add_auto_activated_keyed_singleton[TKey, TService](
        self,
        service_key: TKey | None,  # pyright: ignore[reportInvalidTypeVarUse]
        service_type: type[TService],
        implementation_type: type,
        /,
    ) -> None: ...

    @overload
    def add_auto_activated_keyed_singleton[TKey, TService](
        self,
        service_key: TKey | None,  # pyright: ignore[reportInvalidTypeVarUse]
        service_type: type[TService],
        implementation_instance: object,
        /,
    ) -> None: ...

    def add_auto_activated_keyed_singleton[TKey, TService, *TFactoryParameter](
        self,
        service_key: TKey | None,
        service_type_or_implementation_factory: type[TService]
        | Callable[[TKey | None, *TFactoryParameter], Awaitable[TService]]
        | Callable[[TKey | None, *TFactoryParameter], TService],
        implementation_factory_or_implementation_type_or_implementation_instance_or_none: Callable[
            [TKey | None, *TFactoryParameter], Awaitable[TService]
        ]
        | Callable[[TKey | None, *TFactoryParameter], TService]
        | type
        | object
        | None = None,
        /,
    ) -> None:
        """Add an auto-activated keyed singleton service.

        An auto-activated keyed singleton service is instantiated when the service provider is built (eagerly), rather than when it's first requested (lazily).
        """
        self._add_from_overloaded_constructor(
            lifetime=ServiceLifetime.SINGLETON,
            service_type_or_implementation_factory=service_type_or_implementation_factory,
            implementation_factory_or_implementation_type_or_implementation_instance_or_none=implementation_factory_or_implementation_type_or_implementation_instance_or_none,
            service_key=service_key,
            auto_activate=True,
        )

    def enable_singleton_auto_activation(self, service_type: type) -> None:
        """Mark a registered singleton service as auto-activated.

        An auto-activated singleton service is instantiated when the service provider is built (eagerly), rather than when it's first requested (lazily).
        """
        typed_service_type = TypedType.from_type(service_type)
        is_descriptor_found = False

        for descriptor in self._descriptors:
            if (
                descriptor.service_type == typed_service_type
                and descriptor.lifetime == ServiceLifetime.SINGLETON
            ):
                is_descriptor_found = True
                descriptor.auto_activate = True

        if not is_descriptor_found:
            raise NoSingletonServiceRegisteredError(typed_service_type)

    def enable_keyed_singleton_auto_activation(
        self, service_key: object | None, service_type: type
    ) -> None:
        """Mark a registered keyed singleton service as auto-activated.

        An auto-activated keyed singleton service is instantiated when the service provider is built (eagerly), rather than when it's first requested (lazily).
        """
        typed_service_type = TypedType.from_type(service_type)
        is_descriptor_found = False

        for descriptor in self._descriptors:
            if (
                descriptor.is_keyed_service
                and descriptor.lifetime == ServiceLifetime.SINGLETON
                and descriptor.service_type == typed_service_type
                and descriptor.service_key == service_key
            ):
                is_descriptor_found = True
                descriptor.auto_activate = True

        if not is_descriptor_found:
            raise NoKeyedSingletonServiceRegisteredError(
                service_type=typed_service_type, service_key_type=type(service_key)
            )

    def configure_fastapi(self, app: FastAPI) -> None:
        """Configure the FastAPI application to use dependency injection using the services from this service collection."""
        FastApiDependencyInjection.setup(app, self)

    def _add_from_overloaded_constructor[TService](
        self,
        lifetime: ServiceLifetime,
        service_type_or_implementation_factory: type[TService]
        | Callable[..., Awaitable[TService]]
        | Callable[..., TService],
        implementation_factory_or_implementation_type_or_implementation_instance_or_none: Callable[
            ..., Awaitable[TService]
        ]
        | Callable[..., TService]
        | type
        | object
        | None = None,
        service_key: object | None = WirioUndefined.INSTANCE,
        auto_activate: bool = False,
    ) -> None:
        service_type_to_add: type[TService] | None = None
        implementation_factory_to_add: (
            Callable[..., Awaitable[TService]] | Callable[..., TService] | None
        ) = None
        implementation_type_to_add: type | None = None
        implementation_instance_to_add: object | None = None

        if isinstance(service_type_or_implementation_factory, type):
            service_type_to_add = service_type_or_implementation_factory

        if isinstance(
            implementation_factory_or_implementation_type_or_implementation_instance_or_none,
            type,
        ):
            implementation_type_to_add = implementation_factory_or_implementation_type_or_implementation_instance_or_none
        elif (
            service_type_to_add is not None
            and implementation_factory_or_implementation_type_or_implementation_instance_or_none
            is not None
        ):
            if callable(
                implementation_factory_or_implementation_type_or_implementation_instance_or_none
            ):
                implementation_factory_to_add = cast(
                    "Callable[..., Awaitable[TService]] | Callable[..., TService]",
                    implementation_factory_or_implementation_type_or_implementation_instance_or_none,
                )
            else:
                implementation_instance_to_add = implementation_factory_or_implementation_type_or_implementation_instance_or_none
        elif (
            service_type_to_add is None
            and implementation_factory_or_implementation_type_or_implementation_instance_or_none
            is None
        ):
            implementation_factory_to_add = service_type_or_implementation_factory

        self._add(
            lifetime=lifetime,
            service_type=service_type_to_add,
            implementation_factory=implementation_factory_to_add,
            implementation_type=implementation_type_to_add,
            implementation_instance=implementation_instance_to_add,
            service_key=service_key,
            auto_activate=auto_activate,
        )

    def _add[TService](  # noqa: PLR0913
        self,
        lifetime: ServiceLifetime,
        service_type: type[TService] | None,
        implementation_factory: Callable[..., Awaitable[TService]]
        | Callable[..., TService]
        | None,
        implementation_type: type | None,
        implementation_instance: object | None,
        service_key: object | None,
        auto_activate: bool,
    ) -> None:
        self._ensure_service_container_is_not_built()
        provided_service_type = self._get_provided_service_type(
            service_type, implementation_factory
        )
        is_service_key_provided = service_key is not WirioUndefined.INSTANCE
        service_key_to_add = service_key if is_service_key_provided else None
        service_descriptor: ServiceDescriptor | None = None

        if implementation_instance is not None:
            service_descriptor = ServiceDescriptor.from_implementation_instance(
                service_type=provided_service_type,
                implementation_instance=implementation_instance,
                service_key=service_key_to_add,
                lifetime=lifetime,
                auto_activate=auto_activate,
            )
        elif implementation_factory is None:
            if implementation_type is not None and (
                not issubclass(implementation_type, provided_service_type)
                or provided_service_type == implementation_type
            ):
                error_message = (
                    f"{implementation_type} is not subclass of {provided_service_type}"
                )
                raise TypeError(error_message)

            implementation_type_to_add = (
                implementation_type
                if implementation_type is not None
                else provided_service_type
            )
            service_descriptor = ServiceDescriptor.from_implementation_type(
                service_type=provided_service_type,
                implementation_type=implementation_type_to_add,
                service_key=service_key_to_add,
                lifetime=lifetime,
                auto_activate=auto_activate,
            )
        elif inspect.iscoroutinefunction(implementation_factory):
            if is_service_key_provided:
                service_descriptor = (
                    ServiceDescriptor.from_keyed_async_implementation_factory(
                        service_type=provided_service_type,
                        implementation_factory=implementation_factory,
                        service_key=service_key_to_add,
                        lifetime=lifetime,
                        auto_activate=auto_activate,
                    )
                )
            else:
                service_descriptor = (
                    ServiceDescriptor.from_async_implementation_factory(
                        service_type=provided_service_type,
                        implementation_factory=implementation_factory,
                        lifetime=lifetime,
                        auto_activate=auto_activate,
                    )
                )
        else:  # noqa: PLR5501
            if is_service_key_provided:
                service_descriptor = (
                    ServiceDescriptor.from_keyed_sync_implementation_factory(
                        service_type=provided_service_type,
                        implementation_factory=implementation_factory,
                        service_key=service_key_to_add,
                        lifetime=lifetime,
                        auto_activate=auto_activate,
                    )
                )
            else:
                service_descriptor = ServiceDescriptor.from_sync_implementation_factory(
                    service_type=provided_service_type,
                    implementation_factory=implementation_factory,
                    lifetime=lifetime,
                    auto_activate=auto_activate,
                )

        assert service_descriptor is not None
        self._descriptors.append(service_descriptor)

    def _get_provided_service_type[TService](
        self,
        service_type: type[TService] | None = None,
        implementation_factory: Callable[..., Awaitable[TService]]
        | Callable[..., TService]
        | None = None,
    ) -> type:
        if service_type is not None:
            return service_type

        assert implementation_factory is not None

        type_hints: dict[str, type] = typing.get_type_hints(implementation_factory)
        return_type = type_hints.get("return")

        if return_type is None:
            error_message = "Missing return type hints from 'implementation_factory'"
            raise ValueError(error_message)

        return return_type

    def _ensure_service_container_is_built(self) -> None:
        if self._service_provider is None:
            raise ServiceContainerNotBuiltError

    def _ensure_service_container_is_not_built(self) -> None:
        if self._service_provider is not None:
            raise ServiceContainerAlreadyBuiltError

    @typing.override
    async def __aenter__(self) -> Self:
        await self._get_service_provider()
        return self

    @typing.override
    async def __aexit__(
        self,
        exc_type: type[BaseException] | None,
        exc_val: BaseException | None,
        exc_tb: TracebackType | None,
    ) -> bool | None:
        assert self._service_provider is not None
        await self._service_provider.__aexit__(exc_type, exc_val, exc_tb)
        self._service_provider = None
