import Sparkle
import Foundation
import OSLog

@MainActor
final class SparkleUpdater: NSObject, ObservableObject {
    private lazy var controller: SPUStandardUpdaterController = {
        SPUStandardUpdaterController(startingUpdater: true, updaterDelegate: self, userDriverDelegate: nil)
    }()
    private let logger = Logger(subsystem: "net.24unix.iKeyMon", category: "Sparkle")
    private let verboseLogging: Bool
    @Published var logMessages: [String] = []

    override init() {
        self.verboseLogging = ProcessInfo.processInfo.environment["SPARKLE_VERBOSE_LOGGING"] == "1"
        super.init()
        _ = controller
        log("Sparkle updater initialized (verbose=\(verboseLogging)).")
    }

    var automaticallyChecksForUpdates: Bool {
        get { controller.updater.automaticallyChecksForUpdates }
        set { controller.updater.automaticallyChecksForUpdates = newValue }
    }

    var automaticallyDownloadsUpdates: Bool {
        get { controller.updater.automaticallyDownloadsUpdates }
        set { controller.updater.automaticallyDownloadsUpdates = newValue }
    }

    func checkForUpdates() {
        log("Manual check for updates triggered.")
        controller.checkForUpdates(nil)
    }

    private func log(_ message: String) {
        logger.log("\(message, privacy: .public)")
        addLogMessage("[INFO] \(message)")
        if verboseLogging {
            print("[Sparkle] \(message)")
        }
    }

    private func logError(_ message: String) {
        logger.error("\(message, privacy: .public)")
        addLogMessage("[ERROR] \(message)")
        if verboseLogging {
            fputs("[Sparkle][error] \(message)\n", stderr)
        }
    }

    private func addLogMessage(_ message: String) {
        let timestamp = DateFormatter.localizedString(from: Date(), dateStyle: .none, timeStyle: .medium)
        let timestampedMessage = "[\(timestamp)] \(message)"
        logMessages.append(timestampedMessage)
        if logMessages.count > 100 {
            logMessages.removeFirst()
        }
    }

    private func describe(update item: SUAppcastItem) -> String {
        let short = item.displayVersionString
        let build = item.versionString
        return "\(short) (build \(build))"
    }
}

extension SparkleUpdater: SPUUpdaterDelegate {
    nonisolated func updater(_ updater: SPUUpdater, didFinishLoading appcast: SUAppcast) {
        Task { @MainActor in
            log("Loaded Sparkle appcast containing \(appcast.items.count) item(s).")
        }
    }

    nonisolated func updater(_ updater: SPUUpdater, didFindValidUpdate item: SUAppcastItem) {
        Task { @MainActor in
            log("Found valid update \(describe(update: item))")
        }
    }

    nonisolated func updaterDidNotFindUpdate(_ updater: SPUUpdater) {
        Task { @MainActor in
            log("No updates available.")
        }
    }

    nonisolated func updater(_ updater: SPUUpdater, willDownloadUpdate item: SUAppcastItem, with request: NSMutableURLRequest) {
        Task { @MainActor in
            log("Downloading \(describe(update: item)) from \(request.url?.absoluteString ?? "unknown URL")")
        }
    }

    nonisolated func updater(_ updater: SPUUpdater, didDownloadUpdate item: SUAppcastItem) {
        Task { @MainActor in
            log("Finished downloading \(describe(update: item))")
        }
    }

    nonisolated func updater(_ updater: SPUUpdater, failedToDownloadUpdate item: SUAppcastItem, error: Error) {
        Task { @MainActor in
            logError("Failed to download \(describe(update: item)): \(error.localizedDescription)")
        }
    }

    nonisolated func userDidCancelDownload(_ updater: SPUUpdater) {
        Task { @MainActor in
            log("User cancelled Sparkle download.")
        }
    }

    nonisolated func updater(_ updater: SPUUpdater, willInstallUpdate item: SUAppcastItem) {
        Task { @MainActor in
            log("Will install update \(describe(update: item))")
        }
    }

    nonisolated func updater(_ updater: SPUUpdater, didAbortWithError error: Error) {
        Task { @MainActor in
            let errorDescription = error as NSError
            let details = "Domain: \(errorDescription.domain), Code: \(errorDescription.code), Description: \(error.localizedDescription)"
            logError("Sparkle aborted: \(details)")
            if let underlying = errorDescription.userInfo[NSUnderlyingErrorKey] as? NSError {
                logError("Underlying error: Domain: \(underlying.domain), Code: \(underlying.code), Description: \(underlying.localizedDescription)")
            }
        }
    }
}
