package main

import "testing"

func TestMinesweeperModelRevealViaKey(t *testing.T) {
	m := NewMinesweeperModel(9, 9, 10)
	m.cursor = MinesweeperPos{Row: 4, Col: 4}

	next, _ := m.Update(key("enter"))
	m = next.(MinesweeperModel)
	if m.isError {
		t.Fatalf("неожиданная ошибка: %s", m.message)
	}
	if !m.game.Board[4][4].Revealed {
		t.Fatalf("клетка должна была открыться")
	}
}

func TestMinesweeperModelFlagViaKey(t *testing.T) {
	m := NewMinesweeperModel(9, 9, 10)
	m.cursor = MinesweeperPos{Row: 4, Col: 4}

	next, _ := m.Update(key("f"))
	m = next.(MinesweeperModel)
	if m.isError {
		t.Fatalf("неожиданная ошибка: %s", m.message)
	}
	if !m.game.Board[4][4].Flagged {
		t.Fatalf("клетка должна была получить флаг")
	}

	next, _ = m.Update(key("f"))
	m = next.(MinesweeperModel)
	if m.game.Board[4][4].Flagged {
		t.Fatalf("флаг должен был сняться")
	}
}

func TestMinesweeperModelCursorMovement(t *testing.T) {
	m := NewMinesweeperModel(9, 9, 10)
	m.cursor = MinesweeperPos{Row: 4, Col: 4}

	next, _ := m.Update(key("up"))
	m = next.(MinesweeperModel)
	if m.cursor.Row != 3 {
		t.Errorf("курсор должен был сдвинуться вверх")
	}
	next, _ = m.Update(key("right"))
	m = next.(MinesweeperModel)
	if m.cursor.Col != 5 {
		t.Errorf("курсор должен был сдвинуться вправо")
	}
}

func TestMinesweeperModelCursorClampsAtEdges(t *testing.T) {
	m := NewMinesweeperModel(9, 9, 10)
	m.cursor = MinesweeperPos{Row: 0, Col: 0}

	next, _ := m.Update(key("up"))
	m = next.(MinesweeperModel)
	if m.cursor.Row != 0 {
		t.Errorf("курсор не должен был уйти за верхний край")
	}
	next, _ = m.Update(key("left"))
	m = next.(MinesweeperModel)
	if m.cursor.Col != 0 {
		t.Errorf("курсор не должен был уйти за левый край")
	}
}

func TestMinesweeperModelCannotRevealFlaggedCell(t *testing.T) {
	m := NewMinesweeperModel(9, 9, 10)
	m.cursor = MinesweeperPos{Row: 4, Col: 4}
	next, _ := m.Update(key("f"))
	m = next.(MinesweeperModel)

	next, _ = m.Update(key("enter"))
	m = next.(MinesweeperModel)
	if !m.isError {
		t.Fatalf("ожидалась ошибка открытия помеченной клетки")
	}
}

func TestMinesweeperModelQReturnsToMenu(t *testing.T) {
	m := NewMinesweeperModel(9, 9, 10)
	next, _ := m.Update(key("q"))
	m = next.(MinesweeperModel)
	if !m.backToMenu {
		t.Fatalf("ожидался флаг backToMenu после 'q'")
	}
	if m.quitting {
		t.Fatalf("'q' не должен закрывать всё приложение")
	}
}

func TestMinesweeperModelNewGameAfterResult(t *testing.T) {
	m := NewMinesweeperModel(9, 9, 10)
	m.game.Result = &MinesweeperResult{Won: false}

	next, _ := m.Update(key("n"))
	m = next.(MinesweeperModel)
	if m.game.Result != nil {
		t.Errorf("новая партия не должна была начаться с результатом")
	}
	if m.game.Width != 9 || m.game.Height != 9 || m.game.MineCount != 10 {
		t.Errorf("новая партия должна была сохранить параметры поля")
	}
}

func TestMinesweeperModelSupportsAllPresets(t *testing.T) {
	presets := []struct{ w, h, mines int }{
		{9, 9, 10},
		{16, 16, 40},
		{30, 16, 99},
	}
	for _, p := range presets {
		m := NewMinesweeperModel(p.w, p.h, p.mines)
		if m.game.Width != p.w || m.game.Height != p.h || m.game.MineCount != p.mines {
			t.Errorf("некорректные параметры для пресета %+v: %+v", p, m.game)
		}
	}
}

func TestMinesweeperModelChordViaKeyOnRevealedCell(t *testing.T) {
	m := NewMinesweeperModel(5, 5, 2)
	m.game.minesPlaced = true
	m.game.Board[0][1].IsMine = true
	m.game.Board[1][0].IsMine = true
	for r := 0; r < 5; r++ {
		for c := 0; c < 5; c++ {
			if m.game.Board[r][c].IsMine {
				continue
			}
			n := 0
			for _, nb := range msNeighbors(msp(r, c)) {
				if m.game.inBounds(nb) && m.game.Board[nb.Row][nb.Col].IsMine {
					n++
				}
			}
			m.game.Board[r][c].AdjacentMines = n
		}
	}
	m.game.Board[0][0].Revealed = true
	m.game.RevealedCount++
	m.game.ToggleFlag(msp(0, 1))
	m.game.ToggleFlag(msp(1, 0))

	m.cursor = msp(0, 0)
	next, _ := m.Update(key("enter"))
	m = next.(MinesweeperModel)
	if m.isError {
		t.Fatalf("неожиданная ошибка хорды через клавишу: %s", m.message)
	}
	if !m.game.Board[1][1].Revealed {
		t.Errorf("хорда через enter на открытой клетке должна была открыть соседа (1,1)")
	}
}

// TestMinesweeperModelFullAutoPlaythrough прогоняет TUI-модель через
// последовательность нажатий на всех клетках поля, проверяя, что
// путь через Update не паникует и партия завершается (тем или иным
// исходом).
func TestMinesweeperModelFullAutoPlaythrough(t *testing.T) {
	m := NewMinesweeperModel(9, 9, 10)

	for r := 0; r < 9 && m.game.Result == nil; r++ {
		for c := 0; c < 9 && m.game.Result == nil; c++ {
			m.cursor = MinesweeperPos{Row: r, Col: c}
			if m.game.Board[r][c].Revealed {
				continue
			}
			next, _ := m.Update(key("enter"))
			m = next.(MinesweeperModel)
		}
	}
	if m.game.Result == nil {
		t.Fatalf("партия должна была завершиться после перебора всех клеток")
	}
}
