#!/usr/bin/env python3
"""
Simple DI Container - 軽量依存性注入コンテナ

Purpose: プロジェクト全体の"TODO: DI"箇所を段階的に解決
Architecture: Infrastructure Layer (DDD準拠)
Dependencies: None (標準ライブラリのみ)
"""

import inspect
import os
import re
import sys
import threading
from collections.abc import Callable
from importlib import import_module
from types import UnionType
from typing import Any, ForwardRef, TypeVar, Union, get_args, get_origin, get_type_hints
from unittest.mock import Mock

T = TypeVar("T")

_CONFIGURED = False
_TEST_DEFAULTS_CONFIGURED = False

_KNOWN_TYPE_MODULES = {
    "EpisodeRepository": "noveler.domain.repositories.episode_repository",
    "ProjectRepository": "noveler.domain.repositories.project_repository",
    "QualityRepository": "noveler.domain.repositories.quality_repository",
    "ILoggerService": "noveler.domain.interfaces.logger_service",
}


class DIContainer:
    """軽量依存性注入コンテナ

    Features:
    - シングルトン・トランジェント登録サポート
    - コンストラクタ注入の自動解決
    - インターフェース→実装のマッピング
    - スレッドセーフ
    """

    def __init__(self) -> None:
        self._singletons: dict[type, Any] = {}
        self._transients: dict[type, Callable[[], Any]] = {}
        self._interfaces: dict[type, type] = {}
        self._lock = threading.RLock()

    def register_singleton(self, interface: type[T], implementation: type[T]) -> "DIContainer":
        """シングルトン登録 (アプリケーション全体で1インスタンス)"""
        with self._lock:
            self._interfaces[interface] = implementation
            # インスタンス生成は初回取得時に遅延実行
        return self

    def register_transient(self, interface: type[T], implementation: type[T]) -> "DIContainer":
        """トランジェント登録 (取得時に毎回新しいインスタンス)"""
        with self._lock:
            self._interfaces[interface] = implementation
            self._transients[interface] = lambda: self._create_instance(implementation)
        return self

    def register_instance(self, interface: type[T], instance: T) -> "DIContainer":
        """既存インスタンスの登録 (テスト時のモック等で使用)"""
        with self._lock:
            self._singletons[interface] = instance
        return self

    def get(self, interface: type[T]) -> T:
        """依存性の取得 (自動的にコンストラクタ注入を解決)"""
        with self._lock:
            # シングルトンキャッシュをチェック
            if interface in self._singletons:
                return self._singletons[interface]

            # トランジェント登録をチェック
            if interface in self._transients:
                return self._transients[interface]()

            # インターフェース→実装マッピングをチェック
            if interface in self._interfaces:
                implementation = self._interfaces[interface]
                instance = self._create_instance(implementation)

                # シングルトンとして登録されていない場合はキャッシュしない
                if interface not in self._transients:
                    self._singletons[interface] = instance

                return instance

            # 直接実装クラスが渡された場合
            if inspect.isclass(interface):
                instance = self._create_instance(interface)
                self._singletons[interface] = instance
                return instance

            msg = f"Interface {interface} is not registered in DI container"
            raise ValueError(msg)

    def _create_instance(self, cls: type[T]) -> T:
        """コンストラクタ注入によるインスタンス生成"""
        try:
            # コンストラクタの型ヒント取得
            module_globals = dict(vars(sys.modules.get(cls.__module__, {})))
            self._augment_globals_for_forward_refs(cls, module_globals)

            try:
                type_hints = get_type_hints(cls.__init__, globalns=module_globals)
            except (NameError, TypeError):
                type_hints = {}
            type_hints.pop("return", None)  # 戻り値の型ヒントを除去

            sig = inspect.signature(cls.__init__)
            dependencies = {}
            for param in list(sig.parameters.values())[1:]:  # 'self' を除く
                if param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD):
                    continue

                annotation = type_hints.get(param.name, param.annotation)
                resolved = self._resolve_annotation(annotation)
                if resolved is None:
                    continue

                try:
                    dependencies[param.name] = self.get(resolved)
                except ValueError:
                    if param.default is not inspect._empty:
                        continue
                    raise

            return cls(**dependencies)

        except Exception as e:
            # DI解決失敗時のフォールバック（引数なしコンストラクタ）
            try:
                return cls()
            except Exception:
                msg = f"Failed to create instance of {cls}: {e}"
                raise ValueError(msg)

    def _augment_globals_for_forward_refs(self, cls: type[Any], module_globals: dict[str, Any]) -> None:
        """ForwardRefを解決するためにグローバル名前空間を補完する。"""
        annotations = getattr(cls.__init__, "__annotations__", {}) or {}
        for hint in annotations.values():
            for name in self._extract_forward_names(hint):
                if name in module_globals:
                    continue
                module_path = _KNOWN_TYPE_MODULES.get(name)
                if not module_path:
                    continue
                try:
                    module = import_module(module_path)
                    module_globals[name] = getattr(module, name)
                except (ImportError, AttributeError):
                    continue

    @staticmethod
    def _extract_forward_names(hint: Any) -> set[str]:
        """型ヒント内のForwardRef名称を抽出する。"""
        names: set[str] = set()

        if isinstance(hint, str):
            for token in re.split(r"[^A-Za-z0-9_]+", hint):
                if token:
                    names.add(token)
            return names

        if isinstance(hint, ForwardRef):
            ref = hint.__forward_arg__
            names.update(DIContainer._extract_forward_names(ref))
            return names

        origin = get_origin(hint)
        if origin in (UnionType, Union):
            for arg in get_args(hint):
                names.update(DIContainer._extract_forward_names(arg))
        return names

    @staticmethod
    def _resolve_annotation(annotation: Any) -> type[Any] | None:
        """OptionalやForwardRefを考慮して型を正規化する。"""
        if annotation in (inspect._empty, Any):
            return None

        origin = get_origin(annotation)
        if origin in (UnionType, Union):
            args = [arg for arg in get_args(annotation) if arg is not type(None)]
            if not args:
                return None
            if len(args) == 1:
                return DIContainer._resolve_annotation(args[0])
            return None

        return annotation


# Global DI Container Instance (Singleton Pattern)
_container = DIContainer()


def get_container() -> DIContainer:
    """グローバルDIコンテナの取得

    Usage:
        container = get_container()
        container.register_singleton(IRepository, YamlRepository)
        repository = container.get(IRepository)
    """
    return _container


def configure_dependencies() -> None:
    """アプリケーション起動時の依存関係設定

    Note: 各モジュールの"TODO: DI"箇所をここで段階的に解決していく
    """
    global _CONFIGURED, _TEST_DEFAULTS_CONFIGURED

    container = get_container()

    if not _CONFIGURED:
        # Phase 1 Week 1-2: Domain純粋性回復 - Logger Service DI注入
        try:
            from noveler.domain.interfaces.logger_service import ILoggerService
            from noveler.infrastructure.adapters.logger_service_adapter import LoggerServiceAdapter

            # LoggerServiceFactoryを作成
            class LoggerServiceFactory:
                def __init__(self) -> None:
                    pass

            # LoggerServiceをシングルトンとして登録し、インスタンスを直接作成
            logger_instance = LoggerServiceAdapter("noveler.domain")
            container.register_instance(ILoggerService, logger_instance)

        except ImportError:
            # フォールバック: インポートエラー時はスキップ
            pass

        _CONFIGURED = True

    if _should_register_test_defaults() and not _TEST_DEFAULTS_CONFIGURED:
        _register_test_defaults(container)
        _TEST_DEFAULTS_CONFIGURED = True


# 段階的実装予定:
# 1. Repository Pattern
# container.register_singleton(IEpisodeRepository, YamlEpisodeRepository)
#
# 2. Service Pattern
# container.register_transient(IQualityService, QualityService)
#
# 3. Adapter Pattern
# container.register_singleton(IClaudeAdapter, ClaudeCodeAdapter)


def inject(func: Callable) -> Callable:
    """依存性注入デコレータ (将来実装予定)

    Usage:
        @inject
        def my_function(repository: IRepository):
            pass
    """
    # TODO: 将来実装予定
    return func


def _should_register_test_defaults() -> bool:
    """テスト環境であればTrueを返す。"""
    return bool(os.getenv("PYTEST_CURRENT_TEST") or os.getenv("NOVELER_DI_ENABLE_TEST_DEFAULTS") == "1")


def _register_test_defaults(container: DIContainer) -> None:
    """テスト用の既定リポジトリを登録する。"""
    from noveler.domain.repositories.episode_repository import EpisodeRepository
    from noveler.domain.repositories.project_repository import ProjectRepository

    episode_repository = Mock(spec=EpisodeRepository)
    _episode_store: dict[tuple[str, int], Any] = {}

    def _save_episode(ep: Any, project_id: str) -> None:
        number_obj = getattr(ep, "number", None)
        number_value = getattr(number_obj, "value", None)
        if number_value is None:
            number_value = max((num for pid, num in _episode_store if pid == project_id), default=0) + 1
        _episode_store[(project_id, number_value)] = ep

    def _find_episode(project_id: str, episode_number: int) -> Any | None:
        return _episode_store.get((project_id, episode_number))

    def _next_episode_number(project_id: str) -> int:
        existing = [num for pid, num in _episode_store if pid == project_id]
        return max(existing, default=0) + 1

    episode_repository.save.side_effect = _save_episode
    episode_repository.find_by_project_and_number.side_effect = _find_episode
    episode_repository.get_next_episode_number.side_effect = _next_episode_number

    project_repository = Mock(spec=ProjectRepository)
    project_repository.exists.return_value = True

    container.register_instance(EpisodeRepository, episode_repository)
    container.register_instance(ProjectRepository, project_repository)
