// ── Telemetry Client ─────────────────────────────────────────
// Generic telemetry event queue + batch flush to platform-service.
// Matches the @bytelyst/telemetry-client TypeScript package interface.
// Product apps configure with BLPlatformConfig; no hardcoded product IDs.

import Foundation

/// Telemetry event matching the platform-service /api/telemetry/events schema.
public struct BLTelemetryEvent: Codable, Sendable {
    public let id: String
    public let productId: String
    public let anonymousInstallId: String
    public let sessionId: String
    public let platform: String
    public let channel: String
    public let osFamily: String
    public let osVersion: String
    public let appVersion: String
    public let buildNumber: String
    public let releaseChannel: String
    public let eventType: String
    public let module: String
    public let eventName: String
    public var feature: String?
    public var message: String?
    public var tags: [String: String]?
    public var metrics: [String: Double]?
    public let occurredAt: String
}

/// Generic telemetry client. Queues events in memory and flushes periodically.
/// Thread-safe via NSLock. Fire-and-forget — errors never surface to the user.
public final class BLTelemetryClient {

    private let config: BLPlatformConfig
    private let client: BLPlatformClient

    private var queue: [[String: Any]] = []
    private let queueLock = NSLock()
    private var flushTimer: Timer?

    private let maxQueue: Int
    private let batchSize: Int
    private let flushIntervalSec: TimeInterval

    private let installId: String
    private var sessionId: String

    private let appVersion: String
    private let buildNumber: String
    private let releaseChannel: String
    private let osVersion: String

    /// Optional extra fields added to every event (e.g. deviceModel, locale, timezone).
    public var extraFields: [String: String] = [:]

    public init(
        config: BLPlatformConfig,
        client: BLPlatformClient,
        maxQueue: Int = 200,
        batchSize: Int = 50,
        flushIntervalSec: TimeInterval = 30,
        releaseChannel: String = "beta"
    ) {
        self.config = config
        self.client = client
        self.maxQueue = maxQueue
        self.batchSize = batchSize
        self.flushIntervalSec = flushIntervalSec
        self.releaseChannel = releaseChannel

        let bundle = Bundle.main
        appVersion = bundle.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"
        buildNumber = bundle.infoDictionary?["CFBundleVersion"] as? String ?? "0"
        osVersion = ProcessInfo.processInfo.operatingSystemVersionString

        // Install ID — persisted in UserDefaults (or App Group if configured)
        let storageKey = "\(config.productId)-telemetry-install-id"
        let defaults: UserDefaults
        if let groupId = config.appGroupId, let groupDefaults = UserDefaults(suiteName: groupId) {
            defaults = groupDefaults
        } else {
            defaults = .standard
        }
        if let existing = defaults.string(forKey: storageKey), !existing.isEmpty {
            installId = existing
        } else {
            let newId = UUID().uuidString
            defaults.set(newId, forKey: storageKey)
            installId = newId
        }

        sessionId = UUID().uuidString
    }

    // MARK: - Lifecycle

    /// Start the periodic flush timer. Call on app launch / foreground.
    public func start() {
        sessionId = UUID().uuidString
        guard flushTimer == nil else { return }
        flushTimer = Timer.scheduledTimer(withTimeInterval: flushIntervalSec, repeats: true) { [weak self] _ in
            self?.flush()
        }
    }

    /// Stop the flush timer and flush remaining events. Call on app background.
    public func stop() {
        flush()
        flushTimer?.invalidate()
        flushTimer = nil
    }

    // MARK: - Track

    /// Track a telemetry event. Thread-safe.
    public func trackEvent(
        _ eventType: String,
        module: String,
        name: String,
        feature: String? = nil,
        message: String? = nil,
        tags: [String: String]? = nil,
        metrics: [String: Double]? = nil,
        userId: String? = nil
    ) {
        var event: [String: Any] = [
            "id": UUID().uuidString,
            "productId": config.productId,
            "anonymousInstallId": installId,
            "sessionId": sessionId,
            "platform": config.platform,
            "channel": config.channel,
            "osFamily": "ios",
            "osVersion": osVersion,
            "appVersion": appVersion,
            "buildNumber": buildNumber,
            "releaseChannel": releaseChannel,
            "eventType": eventType,
            "module": module,
            "eventName": name,
            "occurredAt": ISO8601DateFormatter().string(from: Date()),
        ]

        if let feature { event["feature"] = feature }
        if let message { event["message"] = String(message.prefix(512)) }
        if let tags { event["tags"] = tags }
        if let metrics { event["metrics"] = metrics }
        if let userId { event["userId"] = userId }

        for (key, value) in extraFields {
            event[key] = value
        }

        enqueue(event)
    }

    /// Convenience: track a screen view.
    public func trackScreen(_ screen: String) {
        trackEvent("info", module: "navigation", name: "screen_view", tags: ["screen": screen])
    }

    // MARK: - Flush

    /// Flush all queued events to the server. Thread-safe.
    public func flush() {
        queueLock.lock()
        let events = queue
        queue.removeAll()
        queueLock.unlock()

        guard !events.isEmpty else { return }

        // Batch into chunks
        let chunks = stride(from: 0, to: events.count, by: batchSize).map {
            Array(events[$0..<min($0 + batchSize, events.count)])
        }

        for chunk in chunks {
            sendBatch(chunk)
        }
    }

    // MARK: - Accessors

    public func getInstallId() -> String { installId }
    public func getSessionId() -> String { sessionId }

    // MARK: - Private

    private func enqueue(_ event: [String: Any]) {
        queueLock.lock()
        queue.append(event)
        if queue.count > maxQueue {
            queue.removeFirst(queue.count - maxQueue)
        }
        let count = queue.count
        queueLock.unlock()

        if count >= batchSize {
            flush()
        }
    }

    private func sendBatch(_ events: [[String: Any]]) {
        let body: [String: Any] = [
            "productId": config.productId,
            "events": events,
        ]

        guard let jsonData = try? JSONSerialization.data(withJSONObject: body) else { return }
        client.fireAndForget(path: "/api/telemetry/events", body: jsonData)
    }
}
