from collections.abc import Callable
from typing import Any
from unittest.mock import MagicMock

import pytest
from injector import Provider, ScopeDecorator, singleton

from private_gpt.di import create_application_injector
from private_gpt.settings.settings import Settings, unsafe_settings
from private_gpt.settings.settings_loader import merge_settings
from private_gpt.utils.custom_typing import T


class MockInjector:
    def __init__(self) -> None:
        self.test_injector = create_application_injector()

    def bind_mock(
        self,
        interface: type[T],
        mock: (T | (Callable[..., T] | Provider[T])) | None = None,
        *,
        scope: ScopeDecorator = singleton,
    ) -> T:
        if mock is None:
            mock = MagicMock()
        self.test_injector.binder.bind(interface, to=mock, scope=scope)
        return mock  # type: ignore

    def bind_settings(self, settings: dict[str, Any]) -> Settings:
        merged = merge_settings([unsafe_settings, settings])
        new_settings = Settings(**merged)
        self.test_injector.binder.bind(Settings, new_settings)
        return new_settings

    def get(self, interface: type[T]) -> T:
        return self.test_injector.get(interface)


@pytest.fixture
def injector() -> MockInjector:
    return MockInjector()
