import Foundation

/**
 * SessionManager handles session and user identification for analytics
 *
 * Manages:
 * - user_id: Persistent UUID stored across app launches (identifies a user/device)
 * - session_id: New UUID generated per app launch (identifies a session)
 * - session_depth: Counter incremented per screen view within a session
 * - page_id: New UUID generated per screen/page view
 *
 * Thread-safe singleton pattern ensures consistent IDs across all analytics events.
 */
internal final class SessionManager {

    // MARK: - Singleton

    static let shared = SessionManager()

    // MARK: - Storage Keys

    private enum Keys {
        static let userId = "user_id"
        static let sessionCount = "session_count"
        static let sessionStartTime = "session_start_time"
        static let utmSource = "utm_source"
        static let utmMedium = "utm_medium"
        static let utmCampaign = "utm_campaign"
        static let utmTerm = "utm_term"
        static let utmContent = "utm_content"
    }

    // MARK: - Properties

    private let storage: KeyValueStore
    private let lock = NSLock()

    /// Persistent user ID (stored across app launches)
    private(set) var userId: String

    /// Current session ID (new per app launch)
    private(set) var sessionId: String

    /// Total number of sessions for this user (across all time)
    private(set) var totalSessionCount: Int

    /// True if this is the user's first session
    var isNewUser: Bool {
        return totalSessionCount == 1
    }

    /// Session start time (ISO 8601 string)
    private(set) var sessionStartTime: String

    /// UTM parameters (from deep links or attribution)
    private(set) var utmSource: String?
    private(set) var utmMedium: String?
    private(set) var utmCampaign: String?
    private(set) var utmTerm: String?
    private(set) var utmContent: String?

    /// Session attribution source (derived from UTM or defaults to "direct")
    var sessionSource: String {
        return utmSource ?? "direct"
    }

    /// Session attribution medium (derived from UTM or defaults to "none")
    var sessionMedium: String {
        return utmMedium ?? "none"
    }

    /// Number of page views in current session
    private var _sessionDepth: Int = 0
    var sessionDepth: Int {
        lock.lock()
        defer { lock.unlock() }
        return _sessionDepth
    }

    /// Current page ID (new per page/screen view)
    private var _currentPageId: String?
    var currentPageId: String? {
        lock.lock()
        defer { lock.unlock() }
        return _currentPageId
    }

    // MARK: - Ad Tracking Counters

    /// Number of ad requests in current session
    private var _adRequestCount: Int = 0
    var adRequestCount: Int {
        lock.lock()
        defer { lock.unlock() }
        return _adRequestCount
    }

    /// Number of ad impressions in current session
    private var _adImpressionCount: Int = 0
    var adImpressionCount: Int {
        lock.lock()
        defer { lock.unlock() }
        return _adImpressionCount
    }

    /// Total revenue in micros for current session
    private var _totalRevenueMicros: Int64 = 0
    var totalRevenueMicros: Int64 {
        lock.lock()
        defer { lock.unlock() }
        return _totalRevenueMicros
    }

    // MARK: - Initialization

    private init() {
        self.storage = UserDefaultsStore()

        // Load or generate user_id
        if let existingUserId = storage.getString(key: Keys.userId, default: nil) {
            self.userId = existingUserId
            BCLogger.debug("SessionManager: Loaded existing user_id: \(existingUserId)")
        } else {
            let newUserId = UUID().uuidString
            storage.putString(key: Keys.userId, value: newUserId)
            self.userId = newUserId
            BCLogger.debug("SessionManager: Generated new user_id: \(newUserId)")
        }

        // Load session count and increment
        let sessionCountStr = storage.getString(key: Keys.sessionCount, default: "0") ?? "0"
        let previousCount = Int(sessionCountStr) ?? 0
        self.totalSessionCount = previousCount + 1
        storage.putString(key: Keys.sessionCount, value: String(totalSessionCount))

        // Generate new session_id for this app launch
        self.sessionId = UUID().uuidString

        // Set session start time (ISO 8601 format)
        self.sessionStartTime = ISO8601DateFormatter().string(from: Date())
        storage.putString(key: Keys.sessionStartTime, value: sessionStartTime)

        // Load UTM parameters (if set from deep link)
        self.utmSource = storage.getString(key: Keys.utmSource, default: nil)
        self.utmMedium = storage.getString(key: Keys.utmMedium, default: nil)
        self.utmCampaign = storage.getString(key: Keys.utmCampaign, default: nil)
        self.utmTerm = storage.getString(key: Keys.utmTerm, default: nil)
        self.utmContent = storage.getString(key: Keys.utmContent, default: nil)

        BCLogger.debug("SessionManager: Started session \(totalSessionCount) with session_id: \(sessionId)")
    }

    /// Internal initializer for testing
    internal init(storage: KeyValueStore) {
        self.storage = storage

        // Load or generate user_id
        if let existingUserId = storage.getString(key: Keys.userId, default: nil) {
            self.userId = existingUserId
        } else {
            let newUserId = UUID().uuidString
            storage.putString(key: Keys.userId, value: newUserId)
            self.userId = newUserId
        }

        // Load session count and increment
        let sessionCountStr = storage.getString(key: Keys.sessionCount, default: "0") ?? "0"
        let previousCount = Int(sessionCountStr) ?? 0
        self.totalSessionCount = previousCount + 1
        storage.putString(key: Keys.sessionCount, value: String(totalSessionCount))

        // Generate new session_id
        self.sessionId = UUID().uuidString

        // Set session start time (ISO 8601 format)
        self.sessionStartTime = ISO8601DateFormatter().string(from: Date())
        storage.putString(key: Keys.sessionStartTime, value: sessionStartTime)

        // Load UTM parameters (if set from deep link)
        self.utmSource = storage.getString(key: Keys.utmSource, default: nil)
        self.utmMedium = storage.getString(key: Keys.utmMedium, default: nil)
        self.utmCampaign = storage.getString(key: Keys.utmCampaign, default: nil)
        self.utmTerm = storage.getString(key: Keys.utmTerm, default: nil)
        self.utmContent = storage.getString(key: Keys.utmContent, default: nil)
    }

    // MARK: - Page Tracking

    /**
     * Start a new page view
     *
     * Generates a new page_id and increments session_depth.
     * Should be called at the start of each screen/page view.
     *
     * - Returns: The new page_id
     */
    @discardableResult
    func startPageView() -> String {
        lock.lock()
        defer { lock.unlock() }

        _sessionDepth += 1
        _currentPageId = UUID().uuidString

        BCLogger.debug("SessionManager: Started page view \(_sessionDepth) with page_id: \(_currentPageId!)")

        return _currentPageId!
    }

    /**
     * Get the current page ID, generating one if needed
     *
     * - Returns: The current page_id (creates one if none exists)
     */
    func getOrCreatePageId() -> String {
        lock.lock()
        defer { lock.unlock() }

        if let pageId = _currentPageId {
            return pageId
        }

        // Auto-generate if no page view has been started
        _sessionDepth += 1
        _currentPageId = UUID().uuidString
        return _currentPageId!
    }

    // MARK: - UTM Parameter Management

    /**
     * Set UTM parameters (typically from deep link)
     *
     * This persists UTM parameters for attribution tracking. These will be
     * loaded for future sessions until cleared.
     *
     * - Parameters:
     *   - source: UTM source (e.g., "google", "facebook")
     *   - medium: UTM medium (e.g., "cpc", "email")
     *   - campaign: UTM campaign (e.g., "summer_sale")
     *   - term: UTM term (optional keyword)
     *   - content: UTM content (optional content variant)
     */
    func setUTMParameters(source: String?, medium: String?, campaign: String?, term: String?, content: String?) {
        lock.lock()
        defer { lock.unlock() }

        self.utmSource = source
        self.utmMedium = medium
        self.utmCampaign = campaign
        self.utmTerm = term
        self.utmContent = content

        // Persist to storage
        if let source = source {
            storage.putString(key: Keys.utmSource, value: source)
        }
        if let medium = medium {
            storage.putString(key: Keys.utmMedium, value: medium)
        }
        if let campaign = campaign {
            storage.putString(key: Keys.utmCampaign, value: campaign)
        }
        if let term = term {
            storage.putString(key: Keys.utmTerm, value: term)
        }
        if let content = content {
            storage.putString(key: Keys.utmContent, value: content)
        }

        BCLogger.debug("SessionManager: Set UTM parameters - source: \(source ?? "nil"), medium: \(medium ?? "nil"), campaign: \(campaign ?? "nil")")
    }

    /**
     * Clear UTM parameters
     */
    func clearUTMParameters() {
        lock.lock()
        defer { lock.unlock() }

        self.utmSource = nil
        self.utmMedium = nil
        self.utmCampaign = nil
        self.utmTerm = nil
        self.utmContent = nil

        storage.putString(key: Keys.utmSource, value: nil)
        storage.putString(key: Keys.utmMedium, value: nil)
        storage.putString(key: Keys.utmCampaign, value: nil)
        storage.putString(key: Keys.utmTerm, value: nil)
        storage.putString(key: Keys.utmContent, value: nil)
    }

    // MARK: - Ad Tracking

    /**
     * Increment the ad request counter
     */
    func incrementAdRequestCount() {
        lock.lock()
        defer { lock.unlock() }
        _adRequestCount += 1
        BCLogger.debug("SessionManager: Ad request count: \(_adRequestCount)")
    }

    /**
     * Increment the ad impression counter
     */
    func incrementAdImpressionCount() {
        lock.lock()
        defer { lock.unlock() }
        _adImpressionCount += 1
        BCLogger.debug("SessionManager: Ad impression count: \(_adImpressionCount)")
    }

    /**
     * Add revenue to the session total
     *
     * - Parameter micros: Revenue amount in micros (1/1,000,000 of currency unit)
     */
    func addRevenue(_ micros: Int64) {
        lock.lock()
        defer { lock.unlock() }
        _totalRevenueMicros += micros
        BCLogger.debug("SessionManager: Added revenue \(micros) micros, total: \(_totalRevenueMicros)")
    }

    // MARK: - Session Management

    /**
     * Start a new session (resets all session counters)
     *
     * This generates a new session ID and resets all counters.
     * Use this when you want to explicitly start a new session without restarting the app.
     */
    func startNewSession() {
        lock.lock()
        defer { lock.unlock() }

        // Generate new session ID
        sessionId = UUID().uuidString

        // Update session start time
        sessionStartTime = ISO8601DateFormatter().string(from: Date())
        storage.putString(key: Keys.sessionStartTime, value: sessionStartTime)

        // Increment total session count
        totalSessionCount += 1
        storage.putString(key: Keys.sessionCount, value: String(totalSessionCount))

        // Reset session counters
        _sessionDepth = 0
        _currentPageId = nil
        _adRequestCount = 0
        _adImpressionCount = 0
        _totalRevenueMicros = 0

        BCLogger.debug("SessionManager: Started new session \(totalSessionCount) with session_id: \(sessionId)")
    }

    // MARK: - Timestamp Utilities

    /// Date formatter for ISO 8601 timestamps with milliseconds
    private static let timestampFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
        formatter.timeZone = TimeZone(identifier: "UTC")
        formatter.locale = Locale(identifier: "en_US_POSIX")
        return formatter
    }()

    /**
     * Get current timestamp in ISO 8601 format with milliseconds
     *
     * - Returns: ISO 8601 timestamp string (e.g., "2025-01-15T10:30:00.000Z")
     */
    func getCurrentTimestamp() -> String {
        return SessionManager.timestampFormatter.string(from: Date())
    }

    // MARK: - Reset (for testing)

    /// Reset all session state (for testing only)
    internal func resetForTesting() {
        lock.lock()
        defer { lock.unlock() }

        storage.clear()
        _sessionDepth = 0
        _currentPageId = nil
    }
}
