# app/src/trading/market_intelligence/runtime/dependencies.py

from __future__ import annotations

from src.trading.market_intelligence.common.models import EngineRegistration
from src.trading.market_intelligence.common.types import EngineName
from src.trading.market_intelligence.runtime.exceptions import (
    CircularEngineDependencyError,
    MissingEngineDependencyError,
)


class RuntimeDependencies:
    """Разрешение зависимостей между Engine."""

    def resolve(
        self,
        registrations: tuple[EngineRegistration, ...],
    ) -> tuple[EngineRegistration, ...]:
        """Вернуть Engine в порядке выполнения."""
        registration_by_name = self._build_registration_map(registrations)
        graph = self._build_graph(registrations)

        self._validate_dependencies(graph, registration_by_name)

        return self._topological_sort(graph, registration_by_name)

    def _build_registration_map(
        self,
        registrations: tuple[EngineRegistration, ...],
    ) -> dict[EngineName, EngineRegistration]:
        """Построить быстрый доступ к регистрациям по имени Engine."""
        return {
            registration.metadata.name: registration
            for registration in registrations
        }

    def _build_graph(
        self,
        registrations: tuple[EngineRegistration, ...],
    ) -> dict[EngineName, tuple[EngineName, ...]]:
        """Построить граф обязательных зависимостей Engine."""
        return {
            registration.metadata.name: registration.metadata.required_dependencies
            for registration in registrations
        }

    def _validate_dependencies(
        self,
        graph: dict[EngineName, tuple[EngineName, ...]],
        registration_by_name: dict[EngineName, EngineRegistration],
    ) -> None:
        """Проверить наличие всех обязательных зависимостей."""
        for engine_name, dependencies in graph.items():
            for dependency_name in dependencies:
                if dependency_name not in registration_by_name:
                    raise MissingEngineDependencyError(
                        f"Engine '{engine_name}' requires missing dependency "
                        f"'{dependency_name}'"
                    )

    def _topological_sort(
        self,
        graph: dict[EngineName, tuple[EngineName, ...]],
        registration_by_name: dict[EngineName, EngineRegistration],
    ) -> tuple[EngineRegistration, ...]:
        """Выполнить топологическую сортировку Engine."""
        ordered: list[EngineRegistration] = []
        visiting: set[EngineName] = set()
        visited: set[EngineName] = set()

        for engine_name in graph:
            self._visit(
                engine_name=engine_name,
                graph=graph,
                registration_by_name=registration_by_name,
                visiting=visiting,
                visited=visited,
                ordered=ordered,
            )

        return tuple(ordered)

    def _visit(
        self,
        engine_name: EngineName,
        graph: dict[EngineName, tuple[EngineName, ...]],
        registration_by_name: dict[EngineName, EngineRegistration],
        visiting: set[EngineName],
        visited: set[EngineName],
        ordered: list[EngineRegistration],
    ) -> None:
        """Посетить Engine и его зависимости."""
        if engine_name in visited:
            return

        if engine_name in visiting:
            raise CircularEngineDependencyError(
                f"Circular Engine dependency detected for '{engine_name}'"
            )

        visiting.add(engine_name)

        for dependency_name in graph[engine_name]:
            self._visit(
                engine_name=dependency_name,
                graph=graph,
                registration_by_name=registration_by_name,
                visiting=visiting,
                visited=visited,
                ordered=ordered,
            )

        visiting.remove(engine_name)
        visited.add(engine_name)
        ordered.append(registration_by_name[engine_name])