import pytest
import random

random.seed("РФ СЛФ!")

from source.models.base import Cell, Maze
from source.settings import cell_mapping


class TestCellCreation:
    """Тесты создания клетки и начальных значений."""

    def test_coordinates_are_set(self):
        cell = Cell(3, 7)
        assert cell.x == 3
        assert cell.y == 7

    def test_default_flags_are_false(self):
        cell = Cell(0, 0)
        assert cell.is_wall is False
        assert cell.is_start is False
        assert cell.is_exit is False

    def test_create_wall(self):
        cell = Cell(0, 0, is_wall=True)
        assert cell.is_wall is True

    def test_create_start(self):
        cell = Cell(0, 0, is_start=True)
        assert cell.is_start is True

    def test_create_exit(self):
        cell = Cell(0, 0, is_exit=True)
        assert cell.is_exit is True


class TestCellIsPassable:
    """Тесты метода is_possible."""

    def test_empty_cell_is_passable(self):
        cell = Cell(0, 0)
        assert cell.is_possible() is True

    def test_wall_is_not_passable(self):
        cell = Cell(0, 0, is_wall=True)
        assert cell.is_possible() is False

    def test_start_cell_is_passable(self):
        cell = Cell(0, 0, is_start=True)
        assert cell.is_possible() is True

    def test_exit_cell_is_passable(self):
        cell = Cell(0, 0, is_exit=True)
        assert cell.is_possible() is True


class TestCellFlagsAreMutuallyExclusive:
    """Тесты взаимного исключения флагов."""

    def test_set_wall_clears_start(self):
        cell = Cell(0, 0, is_start=True)
        cell.is_wall = True
        assert cell.is_start is False
        assert cell.is_wall is True

    def test_set_wall_clears_exit(self):
        cell = Cell(0, 0, is_exit=True)
        cell.is_wall = True
        assert cell.is_exit is False
        assert cell.is_wall is True

    def test_set_start_clears_wall(self):
        cell = Cell(0, 0, is_wall=True)
        cell.is_start = True
        assert cell.is_wall is False
        assert cell.is_start is True

    def test_set_start_clears_exit(self):
        cell = Cell(0, 0, is_exit=True)
        cell.is_start = True
        assert cell.is_exit is False
        assert cell.is_start is True

    def test_set_exit_clears_wall(self):
        cell = Cell(0, 0, is_wall=True)
        cell.is_exit = True
        assert cell.is_wall is False
        assert cell.is_exit is True

    def test_set_exit_clears_start(self):
        cell = Cell(0, 0, is_start=True)
        cell.is_exit = True
        assert cell.is_start is False
        assert cell.is_exit is True

    def test_unset_wall_does_not_clear_others(self):
        # снятие флага (False) не должно трогать остальные
        cell = Cell(0, 0, is_wall=True)
        cell.is_wall = False
        assert cell.is_start is False
        assert cell.is_exit is False


class TestCellStr:
    """Тесты строкового представления клетки."""

    def test_str_returns_string(self):
        cell = Cell(0, 0)
        assert isinstance(str(cell), str)

    def test_repr_contains_coordinates(self):
        cell = Cell(4, 9)
        assert "4" in repr(cell)
        assert "9" in repr(cell)


class TestMaze:
    def test_default_size(self):
        """Проверка размеров лабиринта со значениями по умолчанию"""
        maze = Maze()
        row, col = maze.shape
        assert row == 10
        assert col == 10

    def test_custom_size(self):
        """Проверка размеров лабиринта с заданными размерами"""
        maze = Maze(size=(7, 3))
        assert maze._width == 7
        assert maze._height == 3

    def test_all_cells_empty_on_init(self):
        """Проверка создания пустого лабиринта с заданными размерами"""
        maze = Maze(size=(3, 3))
        for y in range(3):
            for x in range(3):
                cell = maze.get_cell(x, y)
                assert not cell.is_wall
                assert not cell.is_start
                assert not cell.is_exit

    def test_get_cell_valid(self):
        """Проверка получения объекта Cell из лабиринта функцией `get_cell()`"""
        maze = Maze(size=(5, 5))
        assert isinstance(maze.get_cell(2, 3), Cell)

    def test_get_cell_out_of_bounds(self):
        """Проверка неправильных указанных индексов лабиринта"""
        maze = Maze(size=(5, 5))
        assert maze.get_cell(-1, 0) is None
        assert maze.get_cell(0, -1) is None
        assert maze.get_cell(5, 0) is None
        assert maze.get_cell(0, 5) is None

    def test_center_has_four_neighbors(self):
        """Проверка нахождения соседей"""
        maze = Maze(size=(5, 5))
        assert len(maze.get_neighbors(2, 2)) == 4

    def test_corner_has_two_neighbors(self):
        """Проверка нахождения соседей, когда указанное поле в углу лабиринта"""
        maze = Maze(size=(5, 5))
        assert len(maze.get_neighbors(0, 0)) == 2

    def test_wall_excluded_from_neighbors(self):
        """Проверка что стена не попадает в список соседей"""
        maze = Maze(size=(5, 5))
        maze[1, 2] = cell_mapping["wall"]
        assert all(not n.is_wall for n in maze.get_neighbors(2, 2))

    def test_setitem_wall(self):
        """Проверка установки стены через оператор []"""
        maze = Maze(size=(5, 5))
        maze[0, 0] = cell_mapping["wall"]
        assert maze[0, 0].is_wall is True

    def test_setitem_start(self):
        """Проверка установки старта через оператор []"""
        maze = Maze(size=(5, 5))
        maze[0, 0] = cell_mapping["start"]
        assert maze[0, 0].is_start is True

    def test_setitem_exit(self):
        """Проверка установки выхода через оператор []"""
        maze = Maze(size=(5, 5))
        maze[0, 0] = cell_mapping["exit"]
        assert maze[0, 0].is_exit is True

    def test_setitem_empty_clears_flags(self):
        """Проверка сброса флагов клетки при установке пустого типа"""
        maze = Maze(size=(5, 5))
        maze[0, 0] = cell_mapping["wall"]
        maze[0, 0] = cell_mapping["empty"]
        assert not maze[0, 0].is_wall

    def test_getitem_out_of_bounds_raises(self):
        """Проверка выброса IndexError при обращении к клетке вне границ лабиринта"""
        maze = Maze(size=(5, 5))
        with pytest.raises(IndexError):
            _ = maze[10, 10]

    def test_setitem_invalid_symbol_raises(self):
        """Проверка выброса ValueError при установке неизвестного символа"""
        maze = Maze(size=(5, 5))
        with pytest.raises(ValueError):
            maze[0, 0] = "?"

    def test_str_lines_match_height(self):
        """Проверка что количество строк в строковом представлении совпадает с высотой"""
        maze = Maze(size=(4, 6))
        print(str(maze).splitlines())
        assert len(str(maze).splitlines()) == 6

    def test_str_line_length_matches_width(self):
        """Проверка что длина каждой строки в строковом представлении совпадает с шириной"""
        maze = Maze(size=(5, 3))
        for line in str(maze).strip().splitlines():
            assert len(line) == 5
