//
//  BlackjackEngine.swift
//  Blackjack
//
//  Core game logic for Blackjack.
//

import Foundation
import CasinoKit

/// Manages the Blackjack game rules and shoe.
@Observable
@MainActor
final class BlackjackEngine {
    // MARK: - Properties
    
    /// The card shoe.
    private(set) var shoe: Deck
    
    /// Settings reference for rule variations.
    private let settings: GameSettings
    
    /// Running count for card counting (Hi-Lo system).
    private(set) var runningCount: Int = 0
    
    /// Number of decks in the shoe (reads from current settings).
    var deckCount: Int {
        settings.deckCount.rawValue
    }
    
    /// Cards remaining in shoe.
    var cardsRemaining: Int {
        shoe.cardsRemaining
    }
    
    /// True count (running count / decks remaining).
    var trueCount: Double {
        let decksRemaining = max(1.0, Double(cardsRemaining) / 52.0)
        return Double(runningCount) / decksRemaining
    }
    
    /// Minimum cards needed to safely complete a hand.
    /// Need ~10 cards worst case (player splits once, both hit twice, dealer hits twice).
    private let minimumCardsForHand: Int = 10
    
    /// The cut card position (cards to deal before reshuffling).
    /// Set during reshuffle with slight random variation for realism.
    private var cutCardPosition: Int = 0
    
    /// Realistic default penetration based on deck count.
    /// These match typical casino practices.
    private var defaultPenetration: Double {
        switch deckCount {
        case 1: return 0.60    // 60% - common for single-deck (hand-held)
        case 2: return 0.70    // 70% - typical double-deck pitch game
        default: return 0.75  // 75% - standard for 4-8 deck shoe
        }
    }
    
    /// Whether the shoe needs reshuffling (hit cut card position).
    var needsReshuffle: Bool {
        let cardsDealt = (52 * deckCount) - cardsRemaining
        return cardsDealt >= cutCardPosition
    }
    
    /// Whether there are enough cards to start a new hand.
    var canDealNewHand: Bool {
        cardsRemaining >= minimumCardsForHand
    }
    
    /// Current penetration percentage (how much of the shoe has been dealt).
    var penetrationPercentage: Double {
        let totalCards = 52 * deckCount
        return Double(totalCards - cardsRemaining) / Double(totalCards)
    }
    
    // MARK: - Initialization
    
    init(settings: GameSettings) {
        self.settings = settings
        self.shoe = Deck(deckCount: settings.deckCount.rawValue)
        shoe.shuffle()
        
        // Set initial cut card position
        let totalCards = 52 * settings.deckCount.rawValue
        cutCardPosition = Int(Double(totalCards) * defaultPenetration)
    }
    
    // MARK: - Shoe Management
    
    /// Reshuffles the shoe with the current deck count from settings.
    func reshuffle() {
        shoe = Deck(deckCount: settings.deckCount.rawValue)
        shoe.shuffle()
        runningCount = 0
        
        // Calculate cut card position with slight random variation (±5%) for realism
        let totalCards = 52 * deckCount
        let basePenetration = defaultPenetration
        let variation = Double.random(in: 0.95...1.05)
        let cardsToDeal = Int(Double(totalCards) * basePenetration * variation)
        
        // Clamp between 50% and 85% penetration
        let minCards = Int(Double(totalCards) * 0.50)
        let maxCards = Int(Double(totalCards) * 0.85)
        cutCardPosition = max(min(cardsToDeal, maxCards), minCards)
    }
    
    /// Deals a single card from the shoe and updates the running count.
    /// If the shoe is empty, automatically reshuffles and deals.
    func dealCard() -> Card? {
        // Emergency reshuffle if shoe is empty (should rarely happen)
        if cardsRemaining == 0 {
            reshuffle()
        }
        
        guard let card = shoe.draw() else { return nil }
        runningCount += card.hiLoValue
        return card
    }
    
    // MARK: - Card Counting
    
    /// Updates the running count when a card is revealed (e.g., dealer hole card).
    func updateCount(for card: Card) {
        runningCount += card.hiLoValue
    }
    
    // MARK: - Hand Evaluation
    
    /// Determines if dealer should hit based on rules.
    func dealerShouldHit(hand: BlackjackHand) -> Bool {
        let value = hand.value
        
        if value < 17 {
            return true
        }
        
        if value == 17 && hand.isSoft && settings.dealerHitsSoft17 {
            return true
        }
        
        return false
    }
    
    /// Determines the result of a player hand against dealer.
    func determineResult(playerHand: BlackjackHand, dealerHand: BlackjackHand) -> HandResult {
        let playerValue = playerHand.value
        let dealerValue = dealerHand.value
        
        // Player busted
        if playerHand.isBusted {
            return .bust
        }
        
        // Player has blackjack
        if playerHand.isBlackjack {
            if dealerHand.isBlackjack {
                return .push
            }
            return .blackjack
        }
        
        // Dealer busted
        if dealerHand.isBusted {
            return .win
        }
        
        // Dealer has blackjack (player doesn't)
        if dealerHand.isBlackjack {
            return .lose
        }
        
        // Compare values
        if playerValue > dealerValue {
            return .win
        } else if playerValue < dealerValue {
            return .lose
        } else {
            return .push
        }
    }
    
    /// Calculates payout for a hand result.
    func calculatePayout(bet: Int, result: HandResult, isDoubled: Bool) -> Int {
        let effectiveBet = isDoubled ? bet * 2 : bet
        
        switch result {
        case .blackjack:
            return Int(Double(bet) * settings.blackjackPayout) + bet
        case .win:
            return effectiveBet * 2
        case .push:
            return effectiveBet
        case .lose, .bust:
            return 0
        case .surrender:
            return bet / 2
        case .insuranceWin:
            return bet * 3  // 2:1 + original bet
        case .insuranceLose:
            return 0
        }
    }
    
    // MARK: - Action Availability
    
    /// Whether player can double down on this hand.
    func canDoubleDown(hand: BlackjackHand, balance: Int) -> Bool {
        guard hand.cards.count == 2 else { return false }
        guard !hand.isDoubledDown else { return false }
        guard balance >= hand.bet else { return false }
        
        // After split, check DAS rule
        if hand.isSplit && !settings.doubleAfterSplit {
            return false
        }
        
        return true
    }
    
    /// Whether player can split this hand.
    func canSplit(hand: BlackjackHand, balance: Int, currentSplitCount: Int) -> Bool {
        guard hand.canSplit else { return false }
        guard balance >= hand.bet else { return false }
        guard currentSplitCount < 3 else { return false }  // Max 4 hands
        
        // Check resplit aces
        if hand.isSplit && hand.cards.first?.rank == .ace && !settings.resplitAces {
            return false
        }
        
        return true
    }
    
    /// Whether player can surrender.
    func canSurrender(hand: BlackjackHand) -> Bool {
        guard settings.lateSurrender else { return false }
        guard hand.cards.count == 2 else { return false }
        guard !hand.isSplit else { return false }
        return true
    }
    
    /// Whether insurance should be offered.
    func shouldOfferInsurance(dealerUpCard: Card) -> Bool {
        settings.insuranceAllowed && dealerUpCard.rank == .ace
    }
    
    // MARK: - Basic Strategy Hint
    
    /// Returns the basic strategy recommendation based on BJA chart.
    /// Accounts for game settings (surrender, dealer hits soft 17, etc.)
    func getHint(playerHand: BlackjackHand, dealerUpCard: Card) -> String {
        let playerValue = playerHand.value
        let dealerValue = dealerUpCard.blackjackValue
        let isSoft = playerHand.isSoft
        let canDouble = playerHand.cards.count == 2
        let surrenderAvailable = settings.lateSurrender
        let dealerHitsS17 = settings.dealerHitsSoft17
        
        // SURRENDER (when available) - check first
        if surrenderAvailable && playerHand.cards.count == 2 {
            // 16 vs 9, 10, A - Surrender
            if playerValue == 16 && !isSoft && (dealerValue >= 9 || dealerValue == 1) {
                return String(localized: "Surrender")
            }
            // 15 vs 10 - Surrender
            if playerValue == 15 && !isSoft && dealerValue == 10 {
                return String(localized: "Surrender")
            }
            // 15 vs A - Surrender (if dealer hits soft 17)
            if playerValue == 15 && !isSoft && dealerValue == 1 && dealerHitsS17 {
                return String(localized: "Surrender")
            }
        }
        
        // PAIRS
        if playerHand.canSplit {
            let pairRank = playerHand.cards[0].rank
            switch pairRank {
            case .ace:
                return String(localized: "Split")
            case .eight:
                return String(localized: "Split")
            case .ten, .jack, .queen, .king:
                return String(localized: "Stand")
            case .five:
                // Never split 5s - treat as hard 10
                return (canDouble && dealerValue <= 9) ? String(localized: "Double") : String(localized: "Hit")
            case .four:
                // Split 4s vs 5-6 (if DAS), otherwise hit
                return (settings.doubleAfterSplit && (dealerValue == 5 || dealerValue == 6)) 
                    ? String(localized: "Split") : String(localized: "Hit")
            case .two, .three:
                // Split 2s/3s vs 2-7
                return dealerValue >= 2 && dealerValue <= 7 ? String(localized: "Split") : String(localized: "Hit")
            case .six:
                // Split 6s vs 2-6
                return dealerValue >= 2 && dealerValue <= 6 ? String(localized: "Split") : String(localized: "Hit")
            case .seven:
                // Split 7s vs 2-7
                return dealerValue >= 2 && dealerValue <= 7 ? String(localized: "Split") : String(localized: "Hit")
            case .nine:
                // Split 9s vs 2-6, 8-9. Stand vs 7, 10, A
                if dealerValue == 7 || dealerValue == 10 || dealerValue == 1 {
                    return String(localized: "Stand")
                }
                return String(localized: "Split")
            }
        }
        
        // SOFT HANDS (Ace counted as 11)
        if isSoft {
            switch playerValue {
            case 20, 21:  // A,9 or A,10
                return String(localized: "Stand")
            case 19:  // A,8
                // Double vs 6 if dealer hits S17, otherwise stand
                if canDouble && dealerValue == 6 && dealerHitsS17 {
                    return String(localized: "Double")
                }
                return String(localized: "Stand")
            case 18:  // A,7
                // Double vs 3-6, Stand vs 2/7/8, Hit vs 9/10/A
                if canDouble && dealerValue >= 3 && dealerValue <= 6 {
                    return String(localized: "Double")
                }
                if dealerValue == 2 || dealerValue == 7 || dealerValue == 8 {
                    return String(localized: "Stand")
                }
                return String(localized: "Hit")
            case 17:  // A,6
                // Double vs 3-6, otherwise hit
                if canDouble && dealerValue >= 3 && dealerValue <= 6 {
                    return String(localized: "Double")
                }
                return String(localized: "Hit")
            case 16, 15:  // A,5 or A,4
                // Double vs 4-6, otherwise hit
                if canDouble && dealerValue >= 4 && dealerValue <= 6 {
                    return String(localized: "Double")
                }
                return String(localized: "Hit")
            case 14, 13:  // A,3 or A,2
                // Double vs 5-6, otherwise hit
                if canDouble && dealerValue >= 5 && dealerValue <= 6 {
                    return String(localized: "Double")
                }
                return String(localized: "Hit")
            default:
                return String(localized: "Hit")
            }
        }
        
        // HARD HANDS
        switch playerValue {
        case 17...21:
            return String(localized: "Stand")
        case 16:
            // Stand vs 2-6, Hit vs 7+
            return dealerValue >= 2 && dealerValue <= 6 ? String(localized: "Stand") : String(localized: "Hit")
        case 15:
            // Stand vs 2-6, Hit vs 7+
            return dealerValue >= 2 && dealerValue <= 6 ? String(localized: "Stand") : String(localized: "Hit")
        case 14:
            // Stand vs 2-6, Hit vs 7+
            return dealerValue >= 2 && dealerValue <= 6 ? String(localized: "Stand") : String(localized: "Hit")
        case 13:
            // Stand vs 2-6, Hit vs 7+
            return dealerValue >= 2 && dealerValue <= 6 ? String(localized: "Stand") : String(localized: "Hit")
        case 12:
            // Stand vs 4-6, Hit vs 2-3 and 7+
            return dealerValue >= 4 && dealerValue <= 6 ? String(localized: "Stand") : String(localized: "Hit")
        case 11:
            // Always double (except vs A in some rules)
            if canDouble {
                return String(localized: "Double")
            }
            return String(localized: "Hit")
        case 10:
            // Double vs 2-9, Hit vs 10/A
            if canDouble && dealerValue >= 2 && dealerValue <= 9 {
                return String(localized: "Double")
            }
            return String(localized: "Hit")
        case 9:
            // Double vs 3-6, Hit otherwise
            if canDouble && dealerValue >= 3 && dealerValue <= 6 {
                return String(localized: "Double")
            }
            return String(localized: "Hit")
        default:  // 8 or less
            return String(localized: "Hit")
        }
    }
    
    /// Returns the count-adjusted strategy recommendation with deviation explanation.
    /// Based on the "Illustrious 18" - the most valuable count-based deviations.
    func getCountAdjustedHint(playerHand: BlackjackHand, dealerUpCard: Card) -> String {
        let basicHint = getHint(playerHand: playerHand, dealerUpCard: dealerUpCard)
        let tc = Int(trueCount.rounded())
        let playerValue = playerHand.value
        let dealerValue = dealerUpCard.blackjackValue
        let isSoft = playerHand.isSoft
        
        // Helper to format true count with sign
        let tcDisplay = tc >= 0 ? "+\(tc)" : "\(tc)"
        
        // Check for count-based deviations from basic strategy (Illustrious 18)
        
        // 16 vs 10: Stand when TC ≥ 0 (basic strategy says Hit)
        if playerValue == 16 && !isSoft && dealerValue == 10 {
            if tc >= 0 {
                return String(localized: "Stand, not Hit (TC \(tcDisplay))")
            }
        }
        
        // 15 vs 10: Stand when TC ≥ +4 (basic strategy says Hit/Surrender)
        if playerValue == 15 && !isSoft && dealerValue == 10 {
            if tc >= 4 {
                return String(localized: "Stand, not Hit (TC \(tcDisplay))")
            }
        }
        
        // 12 vs 2: Stand when TC ≥ +3 (basic strategy says Hit)
        if playerValue == 12 && !isSoft && dealerValue == 2 {
            if tc >= 3 {
                return String(localized: "Stand, not Hit (TC \(tcDisplay))")
            }
        }
        
        // 12 vs 3: Stand when TC ≥ +2 (basic strategy says Hit)
        if playerValue == 12 && !isSoft && dealerValue == 3 {
            if tc >= 2 {
                return String(localized: "Stand, not Hit (TC \(tcDisplay))")
            }
        }
        
        // 12 vs 4: Hit when TC < 0 (basic strategy says Stand)
        if playerValue == 12 && !isSoft && dealerValue == 4 {
            if tc < 0 {
                return String(localized: "Hit, not Stand (TC \(tcDisplay))")
            }
        }
        
        // 13 vs 2: Hit when TC < -1 (basic strategy says Stand)
        if playerValue == 13 && !isSoft && dealerValue == 2 {
            if tc < -1 {
                return String(localized: "Hit, not Stand (TC \(tcDisplay))")
            }
        }
        
        // 16 vs 9: Stand when TC ≥ +5 (basic strategy says Hit)
        if playerValue == 16 && !isSoft && dealerValue == 9 {
            if tc >= 5 {
                return String(localized: "Stand, not Hit (TC \(tcDisplay))")
            }
        }
        
        // 10 vs 10: Double when TC ≥ +4 (basic strategy says Hit)
        if playerValue == 10 && !isSoft && playerHand.cards.count == 2 && dealerValue == 10 {
            if tc >= 4 {
                return String(localized: "Double, not Hit (TC \(tcDisplay))")
            }
        }
        
        // 10 vs Ace: Double when TC ≥ +4 (basic strategy says Hit)
        if playerValue == 10 && !isSoft && playerHand.cards.count == 2 && dealerValue == 1 {
            if tc >= 4 {
                return String(localized: "Double, not Hit (TC \(tcDisplay))")
            }
        }
        
        // 9 vs 2: Double when TC ≥ +1 (basic strategy says Hit)
        if playerValue == 9 && !isSoft && playerHand.cards.count == 2 && dealerValue == 2 {
            if tc >= 1 {
                return String(localized: "Double, not Hit (TC \(tcDisplay))")
            }
        }
        
        // 9 vs 7: Double when TC ≥ +3 (basic strategy says Hit)
        if playerValue == 9 && !isSoft && playerHand.cards.count == 2 && dealerValue == 7 {
            if tc >= 3 {
                return String(localized: "Double, not Hit (TC \(tcDisplay))")
            }
        }
        
        // Pair of 10s vs 5: Split when TC ≥ +5 (basic strategy says Stand)
        if playerHand.canSplit && playerHand.cards[0].blackjackValue == 10 && dealerValue == 5 {
            if tc >= 5 {
                return String(localized: "Split, not Stand (TC \(tcDisplay))")
            }
        }
        
        // Pair of 10s vs 6: Split when TC ≥ +4 (basic strategy says Stand)
        if playerHand.canSplit && playerHand.cards[0].blackjackValue == 10 && dealerValue == 6 {
            if tc >= 4 {
                return String(localized: "Split, not Stand (TC \(tcDisplay))")
            }
        }
        
        // No deviation applies, return basic strategy
        return basicHint
    }
}

