import Dependencies
import DependenciesMacros
import Foundation
import Sharing
import Utilities

public extension DependencyValues {
    var appSessionCountClient: AppSessionCountClient {
        get { self[AppSessionCountClient.self] }
        set { self[AppSessionCountClient.self] = newValue }
    }
}

public enum SessionStartType {
    case coldStart
    case refocus
}

public enum SessionAuthenticationState: Codable, Equatable {
    case unauthenticated
    case authenticated(userId: String)

    public var userId: String? {
        switch self {
        case .authenticated(let userId): userId
        case .unauthenticated: nil
        }
    }

    public var isAuthenticated: Bool {
        userId != nil
    }
}

public struct UserSessionHistory: Codable, Equatable {
    public var totalSessions: Int = 0
    public var coldStartSessions: Int = 0
    public var refocusSessions: Int = 0
    public var firstAuthenticatedSessionDate: Date?
    public var lastSessionDate: Date?

    public init(
        totalSessions: Int = 0,
        coldStartSessions: Int = 0,
        refocusSessions: Int = 0,
        firstAuthenticatedSessionDate: Date? = nil,
        lastSessionDate: Date? = nil
    ) {
        self.totalSessions = totalSessions
        self.coldStartSessions = coldStartSessions
        self.refocusSessions = refocusSessions
        self.firstAuthenticatedSessionDate = firstAuthenticatedSessionDate
        self.lastSessionDate = lastSessionDate
    }
}

public struct CurrentSessionState: Codable, Equatable {
    public var hasFinishedLaunchingThisSession: Bool = false
    public var hasTransitionThisSession: Bool = false
}

public struct AppSessionData: Codable, Equatable {
    // Overall session counts
    public var totalSessions: Int = 0
    public var coldStartSessions: Int = 0
    public var refocusSessions: Int = 0

    // Authentication state counts
    public var unauthenticatedSessions: Int = 0
    public var authenticatedSessions: Int = 0

    // Per-user detailed tracking
    public var userSessionHistories: [String: UserSessionHistory] = [:]

    // Transition tracking
    public var sessionsWithAuthTransitions: Int = 0
    public var firstTimeLoginSessions: Int = 0

    public var currentSessionState: CurrentSessionState = CurrentSessionState()

    public init(
        totalSessions: Int = 0,
        coldStartSessions: Int = 0,
        refocusSessions: Int = 0,
        unauthenticatedSessions: Int = 0,
        authenticatedSessions: Int = 0,
        userSessionHistories: [String: UserSessionHistory] = [:],
        sessionsWithAuthTransitions: Int = 0,
        firstTimeLoginSessions: Int = 0
    ) {
        self.totalSessions = totalSessions
        self.coldStartSessions = coldStartSessions
        self.refocusSessions = refocusSessions
        self.unauthenticatedSessions = unauthenticatedSessions
        self.authenticatedSessions = authenticatedSessions
        self.userSessionHistories = userSessionHistories
        self.sessionsWithAuthTransitions = sessionsWithAuthTransitions
        self.firstTimeLoginSessions = firstTimeLoginSessions
    }

    public var totalUniqueAuthenticatedUsers: Int {
        userSessionHistories.count
    }

    public func isFirstTimeUser(_ userId: String) -> Bool {
        userSessionHistories[userId]?.firstAuthenticatedSessionDate == nil
    }

    public func getUserSessionCount(_ userId: String) -> Int {
        userSessionHistories[userId]?.totalSessions ?? 0
    }

    public func getUserColdStartSessionCount(_ userId: String) -> Int {
        userSessionHistories[userId]?.coldStartSessions ?? 0
    }
}

public struct AppSessionCountClient {
    /// Tracks when the app finishes launching
    public var trackFinishLaunching: @Sendable () async -> Void

    /// Determines session start type and tracks the session
    public var startSessionFromBecomeActive: @Sendable (
        _ authState: SessionAuthenticationState
    ) async -> SessionStartType

    /// Tracks when authentication state changes during a session
    public var trackAuthenticationTransition: @Sendable (
        _ from: SessionAuthenticationState,
        _ to: SessionAuthenticationState
    ) async -> Void

    /// Gets the current session data
    public var getSessionData: @Sendable () async -> AppSessionData = { .init() }

    /// Checks if this would be a user's first authenticated session
    public var isFirstTimeUser: @Sendable (_ userId: String) async -> Bool = { _ in false }

    /// Gets the number of authenticated sessions for a user
    public var getUserSessionCount: @Sendable (_ userId: String) async -> Int = { _ in 0 }

    /// Gets the number of cold start sessions for a user
    public var getUserColdStartSessionCount: @Sendable (_ userId: String) async -> Int = { _ in 0 }

    /// Resets all session counts (useful for testing or user data deletion)
    public var resetSessionData: @Sendable () async -> Void
}

extension AppSessionCountClient: DependencyKey {
    public static let liveValue: Self = {
        let tracker = SessionTracker()

        return Self(
            trackFinishLaunching: {
                await tracker.trackFinishLaunching()
            },
            startSessionFromBecomeActive: { authState in
                await tracker.startSessionFromBecomeActive(authState: authState)
            },
            trackAuthenticationTransition: { from, to in
                await tracker.trackAuthenticationTransition(from: from, to: to)
            },
            getSessionData: {
                await tracker.getSessionData()
            },
            isFirstTimeUser: { userId in
                await tracker.isFirstTimeUser(userId)
            },
            getUserSessionCount: { userId in
                await tracker.getUserSessionCount(userId)
            },
            getUserColdStartSessionCount: { userId in
                await tracker.getUserColdStartSessionCount(userId)
            },
            resetSessionData: {
                await tracker.resetSessionData()
            }
        )
    }()
}

private actor SessionTracker {
    @Shared(.fileStorage(FilePathKeys.ApplicationSupport.appSessionData.url()))
    var appSessionData: AppSessionData = AppSessionData()

    func trackFinishLaunching() {
        $appSessionData.withLock {
            $0.currentSessionState.hasFinishedLaunchingThisSession = true
        }
    }

    func startSessionFromBecomeActive(
        authState: SessionAuthenticationState
    ) -> SessionStartType {
        var sessionStartType: SessionStartType = .refocus

        $appSessionData.withLock { data in
            sessionStartType = if data.currentSessionState.hasFinishedLaunchingThisSession {
                .coldStart
            } else {
                .refocus
            }

            if sessionStartType == .coldStart {
                data.currentSessionState.hasFinishedLaunchingThisSession = false
            }

            // Reset transition tracking for new session
            data.currentSessionState.hasTransitionThisSession = false
        }

        startSession(startType: sessionStartType, authState: authState)

        return sessionStartType
    }

    func trackAuthenticationTransition(
        from: SessionAuthenticationState,
        to: SessionAuthenticationState
    ) {
        guard from != to else { return }

        var data = appSessionData
        let now = Date()

        // Only count this session as having a transition once
        if !data.currentSessionState.hasTransitionThisSession {
            data.sessionsWithAuthTransitions += 1
            data.currentSessionState.hasTransitionThisSession = true
        }

        switch (from, to) {
        case (.unauthenticated, .authenticated(let userId)):
            data.authenticatedSessions += 1

            let isFirstTime = data.isFirstTimeUser(userId)
            if isFirstTime {
                data.firstTimeLoginSessions += 1
            }

            // Update user session history
            var userHistory = data.userSessionHistories[userId] ?? UserSessionHistory()

            if isFirstTime {
                userHistory.firstAuthenticatedSessionDate = now
            }

            data.userSessionHistories[userId] = userHistory

        case (.authenticated, .unauthenticated):
            data.unauthenticatedSessions += 1

        case (.authenticated, .authenticated),
             (.unauthenticated, .unauthenticated):
            break
        }

        $appSessionData.withLock { $0 = data }
    }

    func getSessionData() -> AppSessionData {
        appSessionData
    }

    func isFirstTimeUser(_ userId: String) -> Bool {
        appSessionData.isFirstTimeUser(userId)
    }

    func getUserSessionCount(_ userId: String) -> Int {
        appSessionData.getUserSessionCount(userId)
    }

    func getUserColdStartSessionCount(_ userId: String) -> Int {
        appSessionData.getUserColdStartSessionCount(userId)
    }

    func resetSessionData() {
        $appSessionData.withLock {
            $0 = AppSessionData()
        }
    }

    private func startSession(
        startType: SessionStartType,
        authState: SessionAuthenticationState
    ) {
        var data = appSessionData
        let now = Date()

        // Reset transition tracking for new session
        data.currentSessionState.hasTransitionThisSession = false

        // Update overall session counts
        data.totalSessions += 1

        switch startType {
        case .coldStart:
            data.coldStartSessions += 1
        case .refocus:
            data.refocusSessions += 1
        }

        // Handle authentication state
        switch authState {
        case .unauthenticated:
            data.unauthenticatedSessions += 1

        case .authenticated(let userId):
            data.authenticatedSessions += 1

            // Check if this is a first-time user
            let isFirstTime = data.isFirstTimeUser(userId)
            if isFirstTime {
                data.firstTimeLoginSessions += 1
            }

            // Update user session history
            var userHistory = data.userSessionHistories[userId] ?? UserSessionHistory()
            userHistory.totalSessions += 1
            userHistory.lastSessionDate = now

            if isFirstTime {
                userHistory.firstAuthenticatedSessionDate = now
            }

            switch startType {
            case .coldStart:
                userHistory.coldStartSessions += 1
            case .refocus:
                userHistory.refocusSessions += 1
            }

            data.userSessionHistories[userId] = userHistory
        }

        $appSessionData.withLock { $0 = data }
    }
}
