//
//  GameSessionProtocol.swift
//  CasinoKit
//
//  Generic protocols for game sessions that work with any casino game.
//  Each game (Blackjack, Baccarat, etc.) provides their own implementation.
//

import Foundation
import SwiftUI

// MARK: - Game-Specific Stats Protocol

/// Protocol for game-specific statistics that each game implements.
/// Example: Blackjack tracks blackjacks, busts, surrenders.
///          Baccarat tracks naturals, banker wins, player wins.
public protocol GameSpecificStats: Codable, Equatable, Sendable {
    /// Creates empty stats.
    init()
    
    /// A list of stat items to display in the UI.
    /// Each game returns its specific stats in a displayable format.
    var displayItems: [StatDisplayItem] { get }
}

/// A single stat item for display in the UI.
public struct StatDisplayItem: Identifiable, Sendable {
    public let id = UUID()
    public let icon: String
    public let iconColor: Color
    public let label: String
    public let value: String
    public let valueColor: Color
    
    public init(
        icon: String,
        iconColor: Color = .white,
        label: String,
        value: String,
        valueColor: Color = .white
    ) {
        self.icon = icon
        self.iconColor = iconColor
        self.label = label
        self.value = value
        self.valueColor = valueColor
    }
}

// MARK: - Round Outcome

/// Outcome of a single round - common to all casino games.
public enum RoundOutcome: String, Codable, Sendable {
    case win
    case lose
    case push
}

// MARK: - Session End Reason

/// Reason why a session ended.
public enum SessionEndReason: String, Codable, Sendable {
    case manualEnd = "ended"      // Player chose to end session
    case brokeOut = "broke"       // Ran out of money
}

// MARK: - Game Session

/// A generic game session that works with any casino game.
/// The Stats type parameter allows each game to track game-specific statistics.
public struct GameSession<Stats: GameSpecificStats>: Codable, Identifiable, Equatable, Sendable {
    
    // MARK: - Identity
    
    /// Unique identifier for this session.
    public let id: UUID
    
    /// The game style/variant used during this session (e.g., "vegas", "european").
    /// This is mutable to stay in sync with current settings.
    public var gameStyle: String
    
    // MARK: - Timing
    
    /// When the session started.
    public let startTime: Date
    
    /// When the session ended (nil if still active).
    public var endTime: Date?
    
    // MARK: - Balance
    
    /// Balance at the start of the session.
    public let startingBalance: Int
    
    /// Current/final balance for this session.
    public var endingBalance: Int
    
    // MARK: - Common Statistics (all games track these)
    
    /// Number of rounds played in this session.
    public var roundsPlayed: Int = 0
    
    /// Number of winning rounds.
    public var wins: Int = 0
    
    /// Number of losing rounds.
    public var losses: Int = 0
    
    /// Number of pushed/tied rounds.
    public var pushes: Int = 0
    
    // MARK: - Financial Statistics
    
    /// Net winnings/losses for this session.
    public var totalWinnings: Int = 0
    
    /// Biggest single round win.
    public var biggestWin: Int = 0
    
    /// Biggest single round loss (stored as negative).
    public var biggestLoss: Int = 0
    
    /// Total amount bet across all rounds.
    public var totalBetAmount: Int = 0
    
    /// Largest single bet placed.
    public var biggestBet: Int = 0
    
    // MARK: - Game-Specific Statistics
    
    /// Game-specific statistics (Blackjack-specific, Baccarat-specific, etc.).
    public var gameStats: Stats
    
    // MARK: - Computed Properties
    
    /// Whether this session is still active.
    public var isActive: Bool {
        endTime == nil
    }
    
    /// Duration of the session in seconds.
    public var duration: TimeInterval {
        let end = endTime ?? Date()
        return end.timeIntervalSince(startTime)
    }
    
    /// Net result (ending balance - starting balance).
    public var netResult: Int {
        endingBalance - startingBalance
    }
    
    /// Win rate as a percentage (0-100).
    public var winRate: Double {
        guard roundsPlayed > 0 else { return 0 }
        return Double(wins) / Double(roundsPlayed) * 100
    }
    
    /// Average bet per round.
    public var averageBet: Int {
        guard roundsPlayed > 0 else { return 0 }
        return totalBetAmount / roundsPlayed
    }
    
    /// How the session ended.
    public var endReason: SessionEndReason? {
        guard !isActive else { return nil }
        if endingBalance == 0 {
            return .brokeOut
        }
        return .manualEnd
    }
    
    // MARK: - Initialization
    
    /// Creates a new active session.
    public init(gameStyle: String, startingBalance: Int) {
        self.id = UUID()
        self.gameStyle = gameStyle
        self.startTime = Date()
        self.endTime = nil
        self.startingBalance = startingBalance
        self.endingBalance = startingBalance
        self.gameStats = Stats()
    }
    
    // MARK: - Recording
    
    /// Records a round with the given outcome and updates stats.
    public mutating func recordRound(
        winnings: Int,
        betAmount: Int,
        outcome: RoundOutcome
    ) {
        roundsPlayed += 1
        totalWinnings += winnings
        totalBetAmount += betAmount
        
        if winnings > biggestWin {
            biggestWin = winnings
        }
        if winnings < biggestLoss {
            biggestLoss = winnings
        }
        if betAmount > biggestBet {
            biggestBet = betAmount
        }
        
        switch outcome {
        case .win:
            wins += 1
        case .lose:
            losses += 1
        case .push:
            pushes += 1
        }
    }
    
    /// Ends the session with the final balance.
    public mutating func end(withBalance balance: Int) {
        endTime = Date()
        endingBalance = balance
    }
    
    /// Updates the ending balance (call after each round).
    public mutating func updateBalance(_ balance: Int) {
        endingBalance = balance
    }
}

// MARK: - Aggregated Stats

/// Aggregated statistics across multiple sessions.
/// Works with any game type.
public struct AggregatedSessionStats: Sendable {
    public var totalSessions: Int = 0
    public var winningSessions: Int = 0
    public var losingSessions: Int = 0
    
    public var totalRoundsPlayed: Int = 0
    public var totalWins: Int = 0
    public var totalLosses: Int = 0
    public var totalPushes: Int = 0
    
    public var totalWinnings: Int = 0
    public var biggestWin: Int = 0
    public var biggestLoss: Int = 0
    public var bestSession: Int = 0
    public var worstSession: Int = 0
    
    public var totalPlayTime: TimeInterval = 0
    public var totalBetAmount: Int = 0
    public var biggestBet: Int = 0
    
    public var winRate: Double {
        guard totalRoundsPlayed > 0 else { return 0 }
        return Double(totalWins) / Double(totalRoundsPlayed) * 100
    }
    
    public var averageBet: Int {
        guard totalRoundsPlayed > 0 else { return 0 }
        return totalBetAmount / totalRoundsPlayed
    }
    
    public var sessionWinRate: Double {
        guard totalSessions > 0 else { return 0 }
        return Double(winningSessions) / Double(totalSessions) * 100
    }
    
    public init() {}
}

// MARK: - Array Extension for Aggregation

extension Array {
    /// Aggregates sessions into combined statistics.
    public func aggregatedStats<Stats: GameSpecificStats>() -> AggregatedSessionStats 
    where Element == GameSession<Stats> {
        var stats = AggregatedSessionStats()
        
        for session in self {
            stats.totalSessions += 1
            stats.totalRoundsPlayed += session.roundsPlayed
            stats.totalWins += session.wins
            stats.totalLosses += session.losses
            stats.totalPushes += session.pushes
            stats.totalWinnings += session.totalWinnings
            stats.totalPlayTime += session.duration
            stats.totalBetAmount += session.totalBetAmount
            
            if session.biggestWin > stats.biggestWin {
                stats.biggestWin = session.biggestWin
            }
            if session.biggestLoss < stats.biggestLoss {
                stats.biggestLoss = session.biggestLoss
            }
            if session.biggestBet > stats.biggestBet {
                stats.biggestBet = session.biggestBet
            }
            
            if session.netResult > 0 {
                stats.winningSessions += 1
                if session.netResult > stats.bestSession {
                    stats.bestSession = session.netResult
                }
            } else if session.netResult < 0 {
                stats.losingSessions += 1
                if session.netResult < stats.worstSession {
                    stats.worstSession = session.netResult
                }
            }
        }
        
        return stats
    }
    
    /// Aggregates game-specific stats from all sessions.
    /// Games should provide their own aggregation logic.
    public func aggregatedGameStats<Stats: GameSpecificStats>() -> Stats
    where Element == GameSession<Stats> {
        var combined = Stats()
        // Game-specific stats need custom aggregation - this returns the last session's stats
        // Each game should implement their own aggregation extension
        if let last = self.last {
            combined = last.gameStats
        }
        return combined
    }
}
