import Testing
@testable import SoliCards

@Suite("Klondike Rules Tests")
struct KlondikeRulesTests {
    let rules = KlondikeRules()

    @Test("Deal creates correct tableau layout")
    func dealLayout() {
        let deck = Deck.standard()
        let snapshot = rules.deal(deck: deck)

        // 7 tableaus with 1,2,3,4,5,6,7 cards
        #expect(snapshot.tableaus.count == 7)
        for i in 0..<7 {
            #expect(snapshot.tableaus[i].count == i + 1)
            // Top card face-up
            #expect(snapshot.tableaus[i].last!.isFaceUp)
            // All others face-down
            for j in 0..<i {
                #expect(!snapshot.tableaus[i][j].isFaceUp)
            }
        }

        // 4 empty foundations
        #expect(snapshot.foundations.count == 4)
        #expect(snapshot.foundations.allSatisfy { $0.isEmpty })

        // Remaining 24 cards in stock
        #expect(snapshot.stock.count == 24)
        #expect(snapshot.waste.isEmpty)
    }

    @Test("Can move ace to empty foundation")
    func aceToFoundation() {
        let ace = Card(suit: .hearts, rank: .ace, isFaceUp: true)
        var snapshot = emptySnapshot()
        snapshot.tableaus[0] = [ace]

        #expect(rules.canMove(cards: [ace], from: .tableau(0), to: .foundation(0), state: snapshot))
    }

    @Test("Cannot move non-ace to empty foundation")
    func nonAceToEmptyFoundation() {
        let two = Card(suit: .hearts, rank: .two, isFaceUp: true)
        var snapshot = emptySnapshot()
        snapshot.tableaus[0] = [two]

        #expect(!rules.canMove(cards: [two], from: .tableau(0), to: .foundation(0), state: snapshot))
    }

    @Test("Can stack alternating colors descending on tableau")
    func tableauStacking() {
        let blackSix = Card(suit: .spades, rank: .six, isFaceUp: true)
        let redFive = Card(suit: .hearts, rank: .five, isFaceUp: true)
        var snapshot = emptySnapshot()
        snapshot.tableaus[0] = [blackSix]
        snapshot.tableaus[1] = [redFive]

        #expect(rules.canMove(cards: [redFive], from: .tableau(1), to: .tableau(0), state: snapshot))
    }

    @Test("Cannot stack same color on tableau")
    func sameColorBlocked() {
        let blackSix = Card(suit: .spades, rank: .six, isFaceUp: true)
        let blackFive = Card(suit: .clubs, rank: .five, isFaceUp: true)
        var snapshot = emptySnapshot()
        snapshot.tableaus[0] = [blackSix]
        snapshot.tableaus[1] = [blackFive]

        #expect(!rules.canMove(cards: [blackFive], from: .tableau(1), to: .tableau(0), state: snapshot))
    }

    @Test("Only king on empty tableau")
    func kingOnEmptyTableau() {
        let king = Card(suit: .hearts, rank: .king, isFaceUp: true)
        let queen = Card(suit: .hearts, rank: .queen, isFaceUp: true)
        var snapshot = emptySnapshot()
        snapshot.tableaus[1] = [king]
        snapshot.tableaus[2] = [queen]

        #expect(rules.canMove(cards: [king], from: .tableau(1), to: .tableau(0), state: snapshot))
        #expect(!rules.canMove(cards: [queen], from: .tableau(2), to: .tableau(0), state: snapshot))
    }

    @Test("Win detection")
    func winDetection() {
        var snapshot = emptySnapshot()
        // Fill all foundations with 13 cards each
        for i in 0..<4 {
            let suit = Suit.allCases[i]
            snapshot.foundations[i] = Rank.allCases.map { Card(suit: suit, rank: $0, isFaceUp: true) }
        }
        #expect(rules.isWon(state: snapshot))
    }

    @Test("Not won with incomplete foundations")
    func notWon() {
        let snapshot = emptySnapshot()
        #expect(!rules.isWon(state: snapshot))
    }

    // MARK: - Helpers

    private func emptySnapshot() -> GameSnapshot {
        GameSnapshot(
            tableaus: Array(repeating: [], count: 7),
            foundations: [[], [], [], []],
            stock: [],
            waste: [],
            freeCells: [],
            moves: 0,
            score: 0
        )
    }
}
