import time
from dataclasses import dataclass

from source.models.base import Maze, Cell
from source.strategy import PathFindingStrategy


@dataclass
class SearchStats:
    """Статистика выполнения поиска пути.

    Attributes:
        elapsed_ms: Время выполнения в миллисекундах.
        visited_count: Количество посещённых клеток.
        path_length: Длина найденного пути (0 если путь не найден).
        path: Найденный путь — список клеток от старта до выхода.
    """

    elapsed_ms: float
    visited_count: int
    path_length: int
    path: list[Cell]

    def __str__(self) -> str:
        return (
            f"Время: {self.elapsed_ms:.3f} мс | "
            f"Посещено клеток: {self.visited_count} | "
            f"Длина пути: {self.path_length}"
        )


class MazeSolver:
    """Оркестратор поиска пути в лабиринте.

    Принимает лабиринт и стратегию поиска, выполняет поиск
    и возвращает результат вместе со статистикой выполнения.

    Example:
        solver = MazeSolver(maze, BFSStrategy())
        stats = solver.solve()
        print(stats)

        solver.set_strategy(AStarStrategy())
        stats = solver.solve()
    """

    def __init__(self, maze: Maze, strategy: PathFindingStrategy) -> None:
        """Инициализирует солвер с лабиринтом и стратегией поиска.

        Args:
            maze: Объект лабиринта.
            strategy: Стратегия поиска пути.
        """
        self._maze = maze
        self._strategy = strategy

    def set_strategy(self, strategy: PathFindingStrategy) -> None:
        """Заменяет текущую стратегию поиска.

        Args:
            strategy: Новая стратегия поиска пути.
        """
        self._strategy = strategy

    def solve(
        self,
        start: Cell = None,
        exit: Cell = None,
    ) -> SearchStats:
        """Выполняет поиск пути и собирает статистику.

        Если start или exit не переданы явно, стратегия найдёт
        их самостоятельно по флагам is_start / is_exit в лабиринте.

        Args:
            start: Стартовая клетка (опционально).
            exit: Конечная клетка (опционально).

        Returns:
            Объект SearchStats с временем выполнения, количеством
            посещённых клеток и длиной найденного пути.
        """
        t_start = time.perf_counter()
        path = self._strategy.find_path(self._maze, start, exit)
        t_end = time.perf_counter()

        elapsed_ms = (t_end - t_start) * 1000

        return SearchStats(
            elapsed_ms=elapsed_ms,
            visited_count=len(path),
            path_length=len(path),
            path=path,
        )
