// ── Crash Reporter ──────────────────────────────────────────
// Generic MetricKit-based crash and performance reporting.
// Stores crash diagnostics locally for debugging and feedback forms.
// Product apps configure with a product-specific persistence key.
// Not available on watchOS — MetricKit is iOS/macOS only.

import Foundation

/// Crash report model stored locally.
/// Available on all platforms (data-only struct).
public struct BLCrashReport: Codable, Identifiable, Sendable {
    public let id: String
    public let date: Date
    public let exceptionType: String?
    public let signal: String?
    public let terminationReason: String?
    public let callStackData: Data?

    public init(date: Date, exceptionType: String?, signal: String?, terminationReason: String?, callStackTree: Data) {
        self.id = UUID().uuidString
        self.date = date
        self.exceptionType = exceptionType
        self.signal = signal
        self.terminationReason = terminationReason
        self.callStackData = callStackTree
    }
}

#if canImport(MetricKit)
import MetricKit

/// Generic MetricKit crash reporter for all ByteLyst iOS/macOS apps.
/// Subscribes to MetricKit, stores crash reports in UserDefaults.
/// Not available on watchOS (MetricKit is iOS 13+ / macOS 12+ only).
@MainActor
public final class BLCrashReporter: NSObject, ObservableObject, MXMetricManagerSubscriber {

    private let persistenceKey: String
    private let maxReports: Int

    @Published public var lastCrashReport: Date?
    @Published public var diagnosticCount: Int = 0

    public init(productId: String, maxReports: Int = 50) {
        self.persistenceKey = "\(productId)-crash-reports"
        self.maxReports = maxReports
        super.init()
        MXMetricManager.shared.add(self)
        loadStats()
    }

    deinit {
        MXMetricManager.shared.remove(self)
    }

    // MARK: - MXMetricManagerSubscriber

    nonisolated public func didReceive(_ payloads: [MXMetricPayload]) {
        // MetricKit delivers daily aggregated metrics — no action needed by default
    }

    nonisolated public func didReceive(_ payloads: [MXDiagnosticPayload]) {
        Task { @MainActor [weak self] in
            guard let self else { return }
            for payload in payloads {
                self.processDiagnosticPayload(payload)
            }
            self.diagnosticCount += payloads.count
            self.lastCrashReport = Date()
        }
    }

    // MARK: - Public API

    /// Get all stored crash reports.
    public func loadCrashReports() -> [BLCrashReport] {
        guard let data = UserDefaults.standard.data(forKey: persistenceKey),
              let reports = try? JSONDecoder().decode([BLCrashReport].self, from: data) else {
            return []
        }
        return reports
    }

    /// Clear all stored crash reports.
    public func clearReports() {
        UserDefaults.standard.removeObject(forKey: persistenceKey)
        diagnosticCount = 0
    }

    // MARK: - Private

    private func processDiagnosticPayload(_ payload: MXDiagnosticPayload) {
        if let crashDiagnostics = payload.crashDiagnostics {
            for crash in crashDiagnostics {
                let report = BLCrashReport(
                    date: Date(),
                    exceptionType: crash.exceptionType?.description,
                    signal: crash.signal?.description,
                    terminationReason: crash.terminationReason?.description,
                    callStackTree: crash.callStackTree.jsonRepresentation()
                )
                storeCrashReport(report)
            }
        }

        if let hangDiagnostics = payload.hangDiagnostics {
            for hang in hangDiagnostics {
                let report = BLCrashReport(
                    date: Date(),
                    exceptionType: nil,
                    signal: nil,
                    terminationReason: "Hang: \(hang.hangDuration.description)",
                    callStackTree: hang.callStackTree.jsonRepresentation()
                )
                storeCrashReport(report)
            }
        }
    }

    private func storeCrashReport(_ report: BLCrashReport) {
        var reports = loadCrashReports()
        reports.append(report)
        if reports.count > maxReports {
            reports = Array(reports.suffix(maxReports))
        }
        if let data = try? JSONEncoder().encode(reports) {
            UserDefaults.standard.set(data, forKey: persistenceKey)
        }
    }

    private func loadStats() {
        diagnosticCount = loadCrashReports().count
    }
}
#endif
