package main

import "testing"

func TestNewKlondikeGameDealsCorrectly(t *testing.T) {
	g := NewKlondikeGame()
	total := 0
	for col := 0; col < 7; col++ {
		if len(g.Tableau[col]) != col+1 {
			t.Errorf("колонна %d: ожидалось %d карт, получено %d", col, col+1, len(g.Tableau[col]))
		}
		for i, kc := range g.Tableau[col] {
			wantFaceUp := i == len(g.Tableau[col])-1
			if kc.FaceUp != wantFaceUp {
				t.Errorf("колонна %d, карта %d: ожидалось FaceUp=%v, получено %v", col, i, wantFaceUp, kc.FaceUp)
			}
		}
		total += len(g.Tableau[col])
	}
	if total != 28 {
		t.Fatalf("ожидалось 28 карт в колоннах (1+2+..+7), получено %d", total)
	}
	if len(g.Stock) != 52-28 {
		t.Fatalf("ожидалось %d карт в колоде добора, получено %d", 52-28, len(g.Stock))
	}
}

func TestDrawStockMovesToWaste(t *testing.T) {
	g := NewKlondikeGame()
	stockBefore := len(g.Stock)
	top := g.Stock[len(g.Stock)-1]

	if err := g.DrawStock(); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if len(g.Stock) != stockBefore-1 {
		t.Fatalf("колода добора должна была уменьшиться на 1")
	}
	if len(g.Waste) != 1 || g.Waste[0] != top {
		t.Fatalf("верхняя карта колоды должна была оказаться в отбое")
	}
}

func TestDrawStockRecyclesWasteWhenEmpty(t *testing.T) {
	g := NewKlondikeGame()
	g.Stock = []Card{c(Two, Hearts), c(Three, Hearts)}
	g.Waste = nil
	if err := g.DrawStock(); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if err := g.DrawStock(); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if len(g.Stock) != 0 {
		t.Fatalf("колода добора должна была опустеть, осталось %d", len(g.Stock))
	}
	if len(g.Waste) != 2 {
		t.Fatalf("в отбое должно быть 2 карты, получено %d", len(g.Waste))
	}

	if err := g.DrawStock(); err != nil {
		t.Fatalf("неожиданная ошибка пересдачи: %v", err)
	}
	// пересдача (переворот отбоя в колоду) — самостоятельное
	// действие; она не добирает карту автоматически в этом же вызове
	if len(g.Stock) != 2 || len(g.Waste) != 0 {
		t.Fatalf("после пересдачи ожидалось 2 карты в колоде и 0 в отбое, получено stock=%d waste=%d",
			len(g.Stock), len(g.Waste))
	}
	if err := g.DrawStock(); err != nil {
		t.Fatalf("неожиданная ошибка добора после пересдачи: %v", err)
	}
	if len(g.Stock) != 1 || len(g.Waste) != 1 {
		t.Fatalf("после добора ожидалось 1 карта в колоде и 1 в отбое, получено stock=%d waste=%d",
			len(g.Stock), len(g.Waste))
	}
}

func TestDrawStockErrorsWhenBothEmpty(t *testing.T) {
	g := NewKlondikeGame()
	g.Stock = nil
	g.Waste = nil
	if err := g.DrawStock(); err != ErrKlondikeEmptySource {
		t.Errorf("ожидалась ErrKlondikeEmptySource, получено: %v", err)
	}
}

func TestCanPlaceOnTableauDescendingAlternating(t *testing.T) {
	g := NewKlondikeGame()
	g.Tableau[0] = []KlondikeCard{{Card: c(Seven, Hearts), FaceUp: true}}

	if !g.canPlaceOnTableau(0, c(Six, Spades)) {
		t.Errorf("чёрная шестёрка должна ложиться на красную семёрку")
	}
	if g.canPlaceOnTableau(0, c(Six, Hearts)) {
		t.Errorf("красная шестёрка не должна ложиться на красную семёрку (нужен другой цвет)")
	}
	if g.canPlaceOnTableau(0, c(Five, Spades)) {
		t.Errorf("пятёрка не должна ложиться на семёрку (не на 1 меньше)")
	}
}

func TestCanPlaceOnEmptyTableauOnlyKing(t *testing.T) {
	g := NewKlondikeGame()
	g.Tableau[0] = nil
	if !g.canPlaceOnTableau(0, c(King, Clubs)) {
		t.Errorf("король должен ложиться в пустую колонну")
	}
	if g.canPlaceOnTableau(0, c(Queen, Clubs)) {
		t.Errorf("дама не должна ложиться в пустую колонну")
	}
}

func TestMoveWasteToTableau(t *testing.T) {
	g := NewKlondikeGame()
	g.Tableau[0] = []KlondikeCard{{Card: c(Seven, Hearts), FaceUp: true}}
	g.Waste = []Card{c(Six, Spades)}

	if err := g.MoveWasteToTableau(0); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if len(g.Waste) != 0 {
		t.Errorf("отбой должен был опустеть")
	}
	if len(g.Tableau[0]) != 2 {
		t.Fatalf("ожидалось 2 карты в колонне, получено %d", len(g.Tableau[0]))
	}
}

func TestMoveWasteToFoundation(t *testing.T) {
	g := NewKlondikeGame()
	g.Waste = []Card{c(Ace, Hearts)}
	if err := g.MoveWasteToFoundation(); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.Foundations[Hearts] != int(Ace) {
		t.Errorf("фундамент червей должен был получить туза")
	}

	g.Waste = []Card{c(Three, Hearts)}
	if err := g.MoveWasteToFoundation(); err != ErrKlondikeInvalidFoundation {
		t.Errorf("ожидалась ошибка (нужна сначала двойка), получено: %v", err)
	}
}

func TestMoveTableauToFoundationFlipsNextCard(t *testing.T) {
	g := NewKlondikeGame()
	g.Tableau[0] = []KlondikeCard{
		{Card: c(Five, Clubs), FaceUp: false},
		{Card: c(Ace, Hearts), FaceUp: true},
	}
	if err := g.MoveTableauToFoundation(0); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if len(g.Tableau[0]) != 1 {
		t.Fatalf("в колонне должна была остаться 1 карта")
	}
	if !g.Tableau[0][0].FaceUp {
		t.Errorf("следующая карта в колонне должна была открыться")
	}
}

func TestMoveTableauToTableauMovesValidRun(t *testing.T) {
	g := NewKlondikeGame()
	g.Tableau[0] = []KlondikeCard{
		{Card: c(Nine, Clubs), FaceUp: false},
		{Card: c(Seven, Hearts), FaceUp: true},
		{Card: c(Six, Spades), FaceUp: true},
		{Card: c(Five, Hearts), FaceUp: true},
	}
	g.Tableau[1] = []KlondikeCard{
		{Card: c(Eight, Spades), FaceUp: true},
	}

	if err := g.MoveTableauToTableau(0, 1); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if len(g.Tableau[0]) != 1 {
		t.Fatalf("в исходной колонне должна была остаться 1 карта (девятка рубашкой), получено %d", len(g.Tableau[0]))
	}
	if !g.Tableau[0][0].FaceUp {
		t.Errorf("оставшаяся девятка должна была автоматически открыться — в этом весь смысл пасьянса")
	}
	if len(g.Tableau[1]) != 4 {
		t.Fatalf("в целевой колонне должно было стать 4 карты, получено %d", len(g.Tableau[1]))
	}
}

func TestMoveTableauToTableauRejectsInvalidDestination(t *testing.T) {
	g := NewKlondikeGame()
	g.Tableau[0] = []KlondikeCard{{Card: c(Seven, Hearts), FaceUp: true}}
	g.Tableau[1] = []KlondikeCard{{Card: c(King, Spades), FaceUp: true}}

	if err := g.MoveTableauToTableau(0, 1); err != ErrKlondikeInvalidTableau {
		t.Errorf("семёрка не должна ложиться на короля, ожидалась ошибка, получено: %v", err)
	}
}

func TestMoveTableauToTableauNPartialMove(t *testing.T) {
	g := NewKlondikeGame()
	g.Tableau[0] = []KlondikeCard{
		{Card: c(King, Spades), FaceUp: true},
		{Card: c(Queen, Diamonds), FaceUp: true},
		{Card: c(Jack, Spades), FaceUp: true},
	}
	g.Tableau[1] = []KlondikeCard{
		{Card: c(King, Clubs), FaceUp: true},
	}

	if err := g.MoveTableauToTableauN(0, 1, 2); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if len(g.Tableau[0]) != 1 || g.Tableau[0][0].Card != c(King, Spades) {
		t.Errorf("в колонне 0 должен был остаться только K♠, получено %v", g.Tableau[0])
	}
	if len(g.Tableau[1]) != 3 {
		t.Fatalf("в колонне 1 ожидалось 3 карты, получено %d", len(g.Tableau[1]))
	}
	if g.Tableau[1][1].Card != c(Queen, Diamonds) || g.Tableau[1][2].Card != c(Jack, Spades) {
		t.Errorf("перенесённые карты должны были сохранить порядок, получено %v", g.Tableau[1])
	}
}

func TestMoveTableauToTableauNRejectsCountBeyondMovableRun(t *testing.T) {
	g := NewKlondikeGame()
	g.Tableau[0] = []KlondikeCard{
		{Card: c(Nine, Clubs), FaceUp: true}, // не в последовательности с тем, что ниже
		{Card: c(King, Spades), FaceUp: true},
		{Card: c(Queen, Diamonds), FaceUp: true},
	}
	g.Tableau[1] = []KlondikeCard{}

	// movableRun здесь всего 2 (K♠-Q♦), запросить 3 нельзя
	if err := g.MoveTableauToTableauN(0, 1, 3); err == nil {
		t.Errorf("ожидалась ошибка при запросе большего числа карт, чем есть в корректной последовательности")
	}
}

func TestMoveTableauToTableauNRejectsZeroOrNegative(t *testing.T) {
	g := NewKlondikeGame()
	g.Tableau[0] = []KlondikeCard{{Card: c(King, Spades), FaceUp: true}}
	g.Tableau[1] = []KlondikeCard{}

	if err := g.MoveTableauToTableauN(0, 1, 0); err == nil {
		t.Errorf("ожидалась ошибка при count=0")
	}
}

func TestMoveTableauToTableauNRejectsInvalidDestinationForPartialRun(t *testing.T) {
	g := NewKlondikeGame()
	g.Tableau[0] = []KlondikeCard{
		{Card: c(King, Spades), FaceUp: true},
		{Card: c(Queen, Diamonds), FaceUp: true},
		{Card: c(Jack, Spades), FaceUp: true},
	}
	// девятка не примет валета (не смежный ранг)
	g.Tableau[1] = []KlondikeCard{
		{Card: c(Nine, Hearts), FaceUp: true},
	}

	if err := g.MoveTableauToTableauN(0, 1, 1); err == nil {
		t.Errorf("ожидалась ошибка — валет не кладётся на девятку")
	}
	if len(g.Tableau[0]) != 3 {
		t.Errorf("при отклонённом ходе колонна-источник не должна была измениться")
	}
}

func TestMoveTableauToTableauStillMovesFullRunByDefault(t *testing.T) {
	g := NewKlondikeGame()
	g.Tableau[0] = []KlondikeCard{
		{Card: c(King, Spades), FaceUp: true},
		{Card: c(Queen, Diamonds), FaceUp: true},
		{Card: c(Jack, Spades), FaceUp: true},
	}
	g.Tableau[1] = []KlondikeCard{}

	if err := g.MoveTableauToTableau(0, 1); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if len(g.Tableau[0]) != 0 {
		t.Errorf("старое поведение по умолчанию должно было перенести всю последовательность целиком")
	}
	if len(g.Tableau[1]) != 3 {
		t.Errorf("ожидалось 3 карты в колонне назначения, получено %d", len(g.Tableau[1]))
	}
}

func TestMovableRunStopsAtBrokenSequence(t *testing.T) {
	g := NewKlondikeGame()
	g.Tableau[0] = []KlondikeCard{
		{Card: c(Nine, Clubs), FaceUp: true},
		{Card: c(Six, Spades), FaceUp: true},
		{Card: c(Five, Hearts), FaceUp: true},
	}
	run := g.movableRun(0)
	if run != 2 {
		t.Errorf("ожидалась цепочка длиной 2 (6-5), получено %d", run)
	}
}

func TestUndoRestoresPreviousState(t *testing.T) {
	g := NewKlondikeGame()
	g.Waste = []Card{c(Ace, Hearts)}
	if err := g.MoveWasteToFoundation(); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if g.Foundations[Hearts] != int(Ace) {
		t.Fatalf("ход должен был примениться")
	}

	if err := g.Undo(); err != nil {
		t.Fatalf("неожиданная ошибка отмены: %v", err)
	}
	if g.Foundations[Hearts] != 0 {
		t.Errorf("после отмены фундамент должен был вернуться в исходное состояние")
	}
	if len(g.Waste) != 1 {
		t.Errorf("после отмены карта должна была вернуться в отбой")
	}
}

func TestUndoErrorsWhenNoHistory(t *testing.T) {
	g := NewKlondikeGame()
	if err := g.Undo(); err != ErrKlondikeNoUndo {
		t.Errorf("ожидалась ErrKlondikeNoUndo, получено: %v", err)
	}
}

func TestWinDetection(t *testing.T) {
	g := NewKlondikeGame()
	for s := Clubs; s <= Spades; s++ {
		g.Foundations[s] = int(King)
	}
	g.Foundations[Hearts] = int(Queen)
	g.Waste = []Card{c(King, Hearts)}
	if err := g.MoveWasteToFoundation(); err != nil {
		t.Fatalf("неожиданная ошибка: %v", err)
	}
	if !g.Won {
		t.Errorf("все 4 фундамента на короле должны означать победу")
	}
}
