//
//  BaccaratEngine.swift
//  Baccarat
//
//  Core game engine implementing all baccarat rules including third card logic.
//

import Foundation
import CasinoKit

/// The baccarat game engine implementing Punto Banco rules.
struct BaccaratEngine {
    private(set) var shoe: Shoe
    private(set) var playerHand: Hand
    private(set) var bankerHand: Hand
    
    /// Creates a new engine with a fresh shoe.
    init(deckCount: Int = 8) {
        shoe = Shoe(deckCount: deckCount)
        playerHand = Hand()
        bankerHand = Hand()
        
        // Burn first card according to casino rules
        shoe.burn(1)
    }
    
    /// Clears hands and checks if shoe needs reshuffling.
    mutating func prepareNewRound() {
        playerHand.clear()
        bankerHand.clear()
        
        if shoe.needsReshuffle {
            shoe.shuffle()
            shoe.burn(1)
        }
    }
    
    /// Deals the initial two cards to both Player and Banker.
    /// Returns the cards in order they were dealt: P1, B1, P2, B2
    mutating func dealInitialCards() -> [Card] {
        var dealtCards: [Card] = []
        
        // Deal alternating: Player, Banker, Player, Banker
        if let p1 = shoe.deal() {
            playerHand.addCard(p1)
            dealtCards.append(p1)
        }
        if let b1 = shoe.deal() {
            bankerHand.addCard(b1)
            dealtCards.append(b1)
        }
        if let p2 = shoe.deal() {
            playerHand.addCard(p2)
            dealtCards.append(p2)
        }
        if let b2 = shoe.deal() {
            bankerHand.addCard(b2)
            dealtCards.append(b2)
        }
        
        return dealtCards
    }
    
    /// Determines if the Player should draw a third card.
    /// Player draws on 0-5, stands on 6-7. Natural (8-9) prevents drawing.
    func shouldPlayerDraw() -> Bool {
        // Check for naturals first
        if playerHand.isNatural || bankerHand.isNatural {
            return false
        }
        
        return playerHand.value <= 5
    }
    
    /// Draws a third card for the Player if rules allow.
    /// - Returns: The drawn card, or nil if Player stands.
    mutating func drawPlayerThirdCard() -> Card? {
        guard shouldPlayerDraw() else { return nil }
        
        if let card = shoe.deal() {
            playerHand.addCard(card)
            return card
        }
        return nil
    }
    
    /// Determines if the Banker should draw a third card.
    /// This follows the complex Punto Banco third card rules.
    func shouldBankerDraw() -> Bool {
        // Check for naturals first
        if playerHand.isNatural || bankerHand.isNatural {
            return false
        }
        
        let bankerValue = bankerHand.value
        
        // If Player didn't draw (stood on 6-7), Banker uses simple rules
        if playerHand.cardCount == 2 {
            return bankerValue <= 5
        }
        
        // Player drew a third card - apply complex Banker rules
        guard let playerThirdCard = playerHand.thirdCard else {
            return bankerValue <= 5
        }
        
        let p3Value = playerThirdCard.baccaratValue
        
        // Banker third card rules based on Banker's total and Player's third card
        switch bankerValue {
        case 0, 1, 2:
            // Banker always draws on 0-2
            return true
            
        case 3:
            // Banker draws unless Player's third card was 8
            return p3Value != 8
            
        case 4:
            // Banker draws if Player's third card was 2-7
            return (2...7).contains(p3Value)
            
        case 5:
            // Banker draws if Player's third card was 4-7
            return (4...7).contains(p3Value)
            
        case 6:
            // Banker draws if Player's third card was 6-7
            return (6...7).contains(p3Value)
            
        case 7:
            // Banker stands on 7
            return false
            
        default:
            // 8-9 are naturals, shouldn't reach here
            return false
        }
    }
    
    /// Draws a third card for the Banker if rules allow.
    /// - Returns: The drawn card, or nil if Banker stands.
    mutating func drawBankerThirdCard() -> Card? {
        guard shouldBankerDraw() else { return nil }
        
        if let card = shoe.deal() {
            bankerHand.addCard(card)
            return card
        }
        return nil
    }
    
    /// Determines the winner of the current round.
    func determineResult() -> GameResult {
        let playerValue = playerHand.value
        let bankerValue = bankerHand.value
        
        if playerValue > bankerValue {
            return .playerWins
        } else if bankerValue > playerValue {
            return .bankerWins
        } else {
            return .tie
        }
    }
    
    // MARK: - Side Bet Checks
    
    /// Whether the Player hand has a pair (first two cards same rank).
    var playerHasPair: Bool {
        guard playerHand.cardCount >= 2,
              let first = playerHand.cards.first,
              let second = playerHand.cards.dropFirst().first else {
            return false
        }
        return first.rank == second.rank
    }
    
    /// Whether the Banker hand has a pair (first two cards same rank).
    var bankerHasPair: Bool {
        guard bankerHand.cardCount >= 2,
              let first = bankerHand.cards.first,
              let second = bankerHand.cards.dropFirst().first else {
            return false
        }
        return first.rank == second.rank
    }
    
    /// The margin of victory for Player (positive) or Banker (negative).
    /// Zero means tie.
    var victoryMargin: Int {
        playerHand.value - bankerHand.value
    }
    
    /// Whether the winning hand had a natural (8 or 9).
    var winnerHadNatural: Bool {
        let result = determineResult()
        switch result {
        case .playerWins: return playerHand.isNatural
        case .bankerWins: return bankerHand.isNatural
        case .tie: return false
        }
    }
    
    // MARK: - Payout Calculations
    
    /// Calculates the payout for a bet given the result.
    /// - Parameters:
    ///   - bet: The bet that was placed.
    ///   - result: The result of the round.
    /// - Returns: The net winnings (positive), net loss (negative), or 0 for push.
    func calculatePayout(bet: Bet, result: GameResult) -> Int {
        switch bet.type {
        case .player, .banker, .tie:
            return calculateMainBetPayout(bet: bet, result: result)
            
        case .playerPair:
            return playerHasPair ? Int(Double(bet.amount) * bet.type.payoutMultiplier) : -bet.amount
            
        case .bankerPair:
            return bankerHasPair ? Int(Double(bet.amount) * bet.type.payoutMultiplier) : -bet.amount
            
        case .dragonBonusPlayer:
            return calculateDragonBonusPayout(bet: bet, forPlayer: true, result: result)
            
        case .dragonBonusBanker:
            return calculateDragonBonusPayout(bet: bet, forPlayer: false, result: result)
        }
    }
    
    /// Calculates payout for main bets (Player, Banker, Tie).
    private func calculateMainBetPayout(bet: Bet, result: GameResult) -> Int {
        if result.isPush(for: bet.type) {
            // Push - bet is returned
            return 0
        }
        
        if result.isWinningBet(bet.type) {
            // Win - return winnings based on payout multiplier
            return Int(Double(bet.amount) * bet.type.payoutMultiplier)
        } else {
            // Loss - lose the bet amount
            return -bet.amount
        }
    }
    
    /// Calculates Dragon Bonus payout.
    private func calculateDragonBonusPayout(bet: Bet, forPlayer: Bool, result: GameResult) -> Int {
        // Determine if the side we bet on won
        let ourSideWon = forPlayer ? (result == .playerWins) : (result == .bankerWins)
        
        if !ourSideWon {
            // Dragon Bonus loses if our side didn't win (including ties)
            return -bet.amount
        }
        
        // Calculate margin
        let margin = abs(victoryMargin)
        let isNatural = forPlayer ? playerHand.isNatural : bankerHand.isNatural
        
        // Get the multiplier
        if let multiplier = DragonBonusPayout.multiplier(for: margin, isNatural: isNatural) {
            return bet.amount * multiplier
        } else {
            // Win by less than 4 - loses
            return -bet.amount
        }
    }
    
    /// Plays a complete round automatically and returns the result.
    /// Used for simulation/testing purposes.
    mutating func playRound() -> GameResult {
        prepareNewRound()
        _ = dealInitialCards()
        _ = drawPlayerThirdCard()
        _ = drawBankerThirdCard()
        return determineResult()
    }
}
