"""
Dependency query service for analyzing artifact dependency graphs.

Provides operations for finding dependents, dependencies, transitive
closures, dependency chains, cycle detection, and build ordering.
"""

from collections import deque
from typing import List, Optional, Set

from markitect.prompts.dependencies.models import (
    CircularDependencyError,
    DependencyGraph,
    EdgeType,
)
from markitect.prompts.dependencies.repository import IDependencyRepository
from markitect.prompts.dependencies.graph import GraphBuilder


class DependencyQueryService:
    """
    Service for querying dependency relationships between artifacts.

    Provides direct and transitive dependency lookups, dependency chain
    computation via BFS, cycle detection, pre-validation, and build
    order (topological sort).
    """

    def __init__(self, repository: IDependencyRepository):
        """
        Initialize with dependency repository.

        Args:
            repository: Repository for reading dependency edges
        """
        self._repository = repository
        self._graph_builder = GraphBuilder(repository)

    def find_dependents(self, artifact_id: str) -> Set[str]:
        """
        Find direct dependents of an artifact (who depends on it).

        Args:
            artifact_id: Artifact to find dependents of

        Returns:
            Set of artifact IDs that directly depend on this artifact
        """
        edges = self._repository.get_by_target(artifact_id)
        return {edge.source_artifact_id for edge in edges}

    def find_dependencies(self, artifact_id: str) -> Set[str]:
        """
        Find direct dependencies of an artifact (what it depends on).

        Args:
            artifact_id: Artifact to find dependencies of

        Returns:
            Set of artifact IDs that this artifact directly depends on
        """
        edges = self._repository.get_by_source(artifact_id)
        return {edge.target_artifact_id for edge in edges}

    def find_transitive_dependents(self, artifact_id: str) -> Set[str]:
        """
        Find all transitive dependents of an artifact (full upstream impact).

        Uses BFS to traverse the reverse dependency graph.

        Args:
            artifact_id: Artifact to find transitive dependents of

        Returns:
            Set of all artifact IDs that transitively depend on this artifact
        """
        graph = self._graph_builder.build_graph()
        visited: Set[str] = set()
        queue = deque([artifact_id])

        while queue:
            current = queue.popleft()
            for predecessor in graph.get_predecessors(current):
                if predecessor not in visited:
                    visited.add(predecessor)
                    queue.append(predecessor)

        return visited

    def find_transitive_dependencies(self, artifact_id: str) -> Set[str]:
        """
        Find all transitive dependencies of an artifact (full dependency tree).

        Uses BFS to traverse the forward dependency graph.

        Args:
            artifact_id: Artifact to find transitive dependencies of

        Returns:
            Set of all artifact IDs this artifact transitively depends on
        """
        graph = self._graph_builder.build_graph()
        visited: Set[str] = set()
        queue = deque([artifact_id])

        while queue:
            current = queue.popleft()
            for successor in graph.get_successors(current):
                if successor not in visited:
                    visited.add(successor)
                    queue.append(successor)

        return visited

    def get_dependency_chain(
        self,
        source_id: str,
        target_id: str,
    ) -> Optional[List[str]]:
        """
        Find a dependency chain between two artifacts using BFS.

        Args:
            source_id: Starting artifact ID
            target_id: Target artifact ID

        Returns:
            List of artifact IDs forming the chain from source to target,
            or None if no path exists
        """
        graph = self._graph_builder.build_graph()

        if source_id == target_id:
            return [source_id]

        visited: Set[str] = {source_id}
        queue: deque[List[str]] = deque([[source_id]])

        while queue:
            path = queue.popleft()
            current = path[-1]

            for successor in graph.get_successors(current):
                if successor == target_id:
                    return path + [successor]
                if successor not in visited:
                    visited.add(successor)
                    queue.append(path + [successor])

        return None

    def detect_circular_dependencies(self) -> List[List[str]]:
        """
        Detect all circular dependencies in the graph.

        Returns:
            List of cycles, where each cycle is a list of artifact IDs
        """
        graph = self._graph_builder.build_graph()
        return graph.detect_cycles()

    def would_create_cycle(
        self,
        source_id: str,
        target_id: str,
    ) -> bool:
        """
        Check if adding an edge would create a cycle.

        Pre-validation check before persisting a new dependency edge.

        Args:
            source_id: Proposed source artifact ID
            target_id: Proposed target artifact ID

        Returns:
            True if adding this edge would create a cycle
        """
        graph = self._graph_builder.build_graph()
        # Adding source -> target creates a cycle if target can reach source
        # (i.e., there's already a path from target to source)
        if source_id == target_id:
            return True

        visited: Set[str] = set()
        queue = deque([target_id])

        while queue:
            current = queue.popleft()
            if current == source_id:
                return True
            for successor in graph.get_successors(current):
                if successor not in visited:
                    visited.add(successor)
                    queue.append(successor)

        return False

    def get_build_order(
        self,
        artifact_ids: Optional[Set[str]] = None,
    ) -> List[str]:
        """
        Get artifacts in build order (topological sort).

        Dependencies appear before the artifacts that depend on them.

        Args:
            artifact_ids: Optional set of artifact IDs to include.
                         If None, returns build order for all artifacts.

        Returns:
            List of artifact IDs in build order

        Raises:
            CircularDependencyError: If graph contains cycles
        """
        graph = self._graph_builder.build_graph(artifact_ids)
        order = graph.topological_sort()
        order.reverse()  # Dependencies before dependents
        return order
