# experiments.py
import time
import csv
from typing import List, Dict
from maze_model import Maze
from maze_builder import TextFileMazeBuilder
from pathfinding_strategies import BFSStrategy, DFSStrategy, AStarStrategy
from maze_solver import MazeSolver, SearchStats


class ExperimentRunner:

    def __init__(self):
        self.builder = TextFileMazeBuilder()
        self.strategies = [
            BFSStrategy(),
            DFSStrategy(),
            AStarStrategy(),
        ]
        self.results: List[Dict] = []

    def create_test_maze_file(self, filename: str, maze_data: List[str]) -> None:
        with open(filename, 'w', encoding='utf-8') as f:
            f.write('\n'.join(maze_data))

    def generate_simple_maze(self) -> List[str]:
        maze = [
            "S        E",
            "          ",
            "          ",
            "          ",
            "          ",
            "          ",
            "          ",
            "          ",
            "          ",
            "          "
        ]
        return maze

    def generate_complex_maze(self, size: int = 50) -> List[str]:
        import random
        random.seed(42)

        maze = []
        for y in range(size):
            row = []
            for x in range(size):
                if (x == 0 and y == 0):
                    row.append('S')
                elif (x == size - 1 and y == size - 1):
                    row.append('E')
                elif random.random() < 0.3:  # 30% стен
                    row.append('#')
                else:
                    row.append(' ')
            maze.append(''.join(row))

        for i in range(size):
            if maze[i][0] == '#':
                row = list(maze[i])
                row[0] = ' '
                maze[i] = ''.join(row)
            if maze[0][i] == '#':
                row = list(maze[0])
                row[i] = ' '
                maze[0] = ''.join(row)

        return maze

    def generate_empty_maze(self, size: int = 50) -> List[str]:
        maze = []
        for y in range(size):
            row = []
            for x in range(size):
                if x == 0 and y == 0:
                    row.append('S')
                elif x == size - 1 and y == size - 1:
                    row.append('E')
                else:
                    row.append(' ')
            maze.append(''.join(row))
        return maze

    def generate_no_exit_maze(self, size: int = 20) -> List[str]:
        maze = []
        for y in range(size):
            row = []
            for x in range(size):
                if x == 0 and y == 0:
                    row.append('S')
                elif x == size - 1 and y == size - 1:
                    row.append('#')  # Выход заблокирован
                else:
                    row.append('#')  # Всё стены
            maze.append(''.join(row))

        # выход в тупике
        row = list(maze[size - 1])
        row[size - 1] = 'E'
        maze[size - 1] = ''.join(row)

        return maze

    def run_experiment(self, maze_name: str, maze_data: List[str],
                       num_runs: int = 5) -> List[Dict]:
        filename = f"test_{maze_name}.txt"
        self.create_test_maze_file(filename, maze_data)

        maze = self.builder.build_from_file(filename)
        results = []

        for strategy in self.strategies:
            solver = MazeSolver(maze, strategy)

            times = []
            path_lengths = []

            for run in range(num_runs):
                stats = solver.solve()
                times.append(stats.time_ms)
                path_lengths.append(stats.path_length)

            avg_time = sum(times) / len(times)
            avg_path_length = sum(path_lengths) / len(path_lengths)

            result = {
                'maze': maze_name,
                'strategy': strategy.name,
                'avg_time_ms': round(avg_time, 3),
                'min_time_ms': round(min(times), 3),
                'max_time_ms': round(max(times), 3),
                'path_length': int(avg_path_length) if avg_path_length else 0,
                'path_found': avg_path_length > 0
            }
            results.append(result)

            print(f"{maze_name} - {strategy.name}: "
                  f"{avg_time:.3f} мс, путь: {int(avg_path_length)}")

        return results

    def run_all_experiments(self):

        experiments = [
            ("simple_10x10", self.generate_simple_maze()),
            ("complex_50x50", self.generate_complex_maze(50)),
            ("large_100x100", self.generate_complex_maze(100)),
            ("empty_50x50", self.generate_empty_maze(50)),
            ("no_exit_20x20", self.generate_no_exit_maze(20))
        ]

        all_results = []

        for name, data in experiments:
            print(f"\n Лабиринт: {name} ---")
            results = self.run_experiment(name, data)
            all_results.extend(results)

        self.save_to_csv(all_results, "experiment_results.csv")



        return all_results

    def save_to_csv(self, results: List[Dict], filename: str):
        if not results:
            return

        with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
            fieldnames = results[0].keys()
            writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
            writer.writeheader()
            writer.writerows(results)


def print_analysis(results: List[Dict]):

    # Группировка
    mazes = set(r['maze'] for r in results)

    for maze in sorted(mazes):
        print(f"\nЛабиринт: {maze}")
        print("-" * 40)

        maze_results = [r for r in results if r['maze'] == maze]

        #по времени
        sorted_results = sorted(maze_results, key=lambda x: x['avg_time_ms'])

        for r in sorted_results:
            status = "✓" if r['path_found'] else "✗"
            print(f"  {status} {r['strategy']:8} | "
                  f"Время: {r['avg_time_ms']:8.3f} мс | "
                  f"Путь: {r['path_length']:4} шагов")

        # Определяем лучший
        fastest = sorted_results[0]
        print(f"\n  → Самый быстрый: {fastest['strategy']} "
              f"({fastest['avg_time_ms']:.3f} мс)")