//
//  SessionManager.swift
//  CasinoKit
//
//  Protocol and utilities for managing game sessions.
//  Games conform to SessionManagedGame to get session management for free.
//

import Foundation
import SwiftUI

// MARK: - Session Managed Game Protocol

/// Protocol for game state classes that use session management.
/// Conforming to this protocol gives you automatic session tracking.
///
/// Example usage in GameState:
/// ```swift
/// @Observable
/// final class GameState: SessionManagedGame {
///     typealias Stats = BlackjackStats
///     var currentSession: GameSession<BlackjackStats>?
///     var sessionHistory: [GameSession<BlackjackStats>] = []
///     // ... rest of implementation
/// }
/// ```
@MainActor
public protocol SessionManagedGame: AnyObject {
    /// The game-specific stats type.
    associatedtype Stats: GameSpecificStats
    
    /// The currently active session (nil if no session).
    var currentSession: GameSession<Stats>? { get set }
    
    /// History of completed sessions.
    var sessionHistory: [GameSession<Stats>] { get set }
    
    /// Current player balance.
    var balance: Int { get set }
    
    /// Starting balance for new sessions (from settings).
    var startingBalance: Int { get }
    
    /// Current game style identifier (e.g., "vegas", "european").
    var currentGameStyle: String { get }
    
    /// Whether the end session confirmation dialog should be shown.
    /// Games should bind this to their confirmation dialog.
    var showEndSessionConfirmation: Bool { get set }
    
    /// Called to persist game data after session changes.
    func saveGameData()
    
    /// Called when starting a new session to reset game-specific state.
    func resetForNewSession()
    
    /// Called before a session is deleted. Override to clean up associated data.
    /// Default implementation does nothing.
    func onWillDeleteSession(id: UUID)
    
    /// Called before all session history is deleted. Override to clean up associated data.
    /// Default implementation does nothing.
    func onWillDeleteAllSessions()
}

// MARK: - Default Session Management Implementation

extension SessionManagedGame {
    
    /// Ensures there's an active session, creating one if needed.
    public func ensureActiveSession() {
        if currentSession == nil {
            startNewSession()
        }
    }
    
    /// Starts a new session.
    /// 
    /// This uses the current `balance` as the session's starting balance. If you need to
    /// start fresh with the default starting balance from settings, call `endSessionAndStartNew()`
    /// instead, which properly resets the balance before creating the new session.
    public func startNewSession() {
        // End current session if exists
        if currentSession != nil {
            endCurrentSession(reason: .manualEnd)
        }
        
        // If balance is 0 or below minimum bet, reset to starting balance from settings.
        // This handles the case where the player went broke and the app was restarted
        // before they clicked "Play Again".
        if balance <= 0 {
            balance = startingBalance
        }
        
        // Create new session with current settings
        currentSession = GameSession<Stats>(
            gameStyle: currentGameStyle,
            startingBalance: balance
        )
        
        saveGameData()
    }
    
    /// Ends the current session and adds it to history.
    public func endCurrentSession(reason: SessionEndReason = .manualEnd) {
        guard var session = currentSession else { return }
        
        session.end(withBalance: balance)
        
        // Add to history (most recent first)
        sessionHistory.insert(session, at: 0)
        
        currentSession = nil
        saveGameData()
    }
    
    /// Ends the current session and starts a fresh one.
    public func endSessionAndStartNew() {
        endCurrentSession(reason: .manualEnd)
        
        // Reset to starting balance
        balance = startingBalance
        
        // Let game reset its specific state
        resetForNewSession()
        
        // Start fresh session
        startNewSession()
    }
    
    /// Called when player runs out of money.
    public func handleSessionGameOver() {
        endCurrentSession(reason: .brokeOut)
    }
    
    /// Records a round result in the current session.
    /// Call this at the end of each round with the outcome.
    public func recordSessionRound(
        winnings: Int,
        betAmount: Int,
        outcome: RoundOutcome,
        updateGameStats: ((inout Stats) -> Void)? = nil
    ) {
        guard var session = currentSession else { return }
        
        // Record common stats
        session.recordRound(
            winnings: winnings,
            betAmount: betAmount,
            outcome: outcome
        )
        
        // Let game update its specific stats
        if let update = updateGameStats {
            update(&session.gameStats)
        }
        
        // Update balance
        session.updateBalance(balance)
        
        currentSession = session
        saveGameData()
    }
    
    // MARK: - Computed Helpers
    
    /// Whether there's an active session.
    public var hasActiveSession: Bool {
        currentSession != nil
    }
    
    /// All sessions including current.
    public var allSessions: [GameSession<Stats>] {
        var sessions: [GameSession<Stats>] = []
        if let current = currentSession {
            sessions.append(current)
        }
        sessions.append(contentsOf: sessionHistory)
        return sessions
    }
    
    /// Aggregated stats from all sessions.
    public var aggregatedStats: AggregatedSessionStats {
        allSessions.aggregatedStats()
    }
    
    /// Sessions filtered by game style.
    public func sessions(forStyle style: String) -> [GameSession<Stats>] {
        allSessions.filter { $0.gameStyle == style }
    }
    
    /// Aggregated stats for a specific style.
    public func aggregatedStats(forStyle style: String) -> AggregatedSessionStats {
        sessions(forStyle: style).aggregatedStats()
    }
    
    // MARK: - Session History Management
    
    /// Default implementation - does nothing. Override to clean up session-specific data.
    public func onWillDeleteSession(id: UUID) {
        // Override in game to clean up associated data (e.g., round histories)
    }
    
    /// Default implementation - does nothing. Override to clean up all session data.
    public func onWillDeleteAllSessions() {
        // Override in game to clean up associated data (e.g., round histories)
    }
    
    /// Deletes a session from history by ID.
    public func deleteSession(id: UUID) {
        onWillDeleteSession(id: id)
        sessionHistory.removeAll { $0.id == id }
        saveGameData()
    }
    
    /// Deletes all session history.
    public func deleteAllSessionHistory() {
        onWillDeleteAllSessions()
        sessionHistory.removeAll()
        saveGameData()
    }
}

// MARK: - Session Formatter

/// Utility for formatting session data for display.
public enum SessionFormatter {
    
    /// Formats a duration as "XXh XXmin".
    public static func formatDuration(_ seconds: TimeInterval) -> String {
        let hours = Int(seconds) / 3600
        let minutes = (Int(seconds) % 3600) / 60
        return String(format: "%02dh %02dmin", hours, minutes)
    }
    
    /// Formats a duration as "X hours, Y minutes" for accessibility.
    public static func formatDurationAccessible(_ seconds: TimeInterval) -> String {
        let hours = Int(seconds) / 3600
        let minutes = (Int(seconds) % 3600) / 60
        
        if hours > 0 {
            return "\(hours) hours, \(minutes) minutes"
        } else {
            return "\(minutes) minutes"
        }
    }
    
    /// Formats money with sign.
    public static func formatMoney(_ amount: Int) -> String {
        if amount >= 0 {
            return "$\(amount)"
        } else {
            return "-$\(abs(amount))"
        }
    }
    
    /// Formats a date as relative time (e.g., "2 hours ago").
    public static func formatRelativeDate(_ date: Date) -> String {
        let formatter = RelativeDateTimeFormatter()
        formatter.unitsStyle = .abbreviated
        return formatter.localizedString(for: date, relativeTo: Date())
    }
    
    /// Formats a date for session display.
    public static func formatSessionDate(_ date: Date) -> String {
        let formatter = DateFormatter()
        formatter.dateStyle = .medium
        formatter.timeStyle = .short
        return formatter.string(from: date)
    }
    
    /// Formats a percentage.
    public static func formatPercent(_ value: Double) -> String {
        value.formatted(.number.precision(.fractionLength(1))) + "%"
    }
}

// MARK: - Session Data Protocol

/// Protocol for game data structs that store sessions.
/// Use this to define your PersistableGameData with session support.
public protocol SessionPersistable {
    associatedtype Stats: GameSpecificStats
    
    var currentSession: GameSession<Stats>? { get set }
    var sessionHistory: [GameSession<Stats>] { get set }
}
