//
//  UpdateCheckService.swift
//  oAI
//
//  Checks for new releases on GitLab and surfaces an update badge in the footer
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Rune Olsen
//
// This file is part of oAI.
//
// oAI is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// oAI is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
// Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with oAI. If not, see <https://www.gnu.org/licenses/>.


import Foundation
#if os(macOS)
import AppKit
#endif
import Observation

@Observable
final class UpdateCheckService {
    static let shared = UpdateCheckService()

    var updateAvailable: Bool = false
    var latestVersion: String? = nil
    var downloadURL: URL? = nil

    // Manual check state — drives the update alert in ContentView
    var isCheckingManually: Bool = false
    var manualCheckMessage: String? = nil

    private let apiURL = "https://gitlab.pm/api/v1/repos/rune/oai-swift/releases/latest"
    private let releasesURL = URL(string: "https://gitlab.pm/rune/oai-swift/releases")!

    private init() {}

    /// Kick off a background update check. Silently does nothing on failure.
    func checkForUpdates() {
        Task.detached(priority: .background) {
            await self.performCheck()
        }
    }

    /// Manual check triggered from the Help menu. Non-blocking — result surfaces via manualCheckMessage.
    func checkForUpdatesManually() {
        guard !isCheckingManually else { return }
        isCheckingManually = true
        Task.detached(priority: .background) {
            await self.performCheck()
            let current = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? ""
            await MainActor.run {
                if self.updateAvailable, let v = self.latestVersion {
                    self.manualCheckMessage = String(localized: "Version \(v) is available.")
                } else {
                    self.manualCheckMessage = String(localized: "You're up to date (v\(current)).")
                }
                self.isCheckingManually = false
            }
        }
    }

    private func performCheck() async {
        guard let url = URL(string: apiURL) else { return }

        var request = URLRequest(url: url)
        request.timeoutInterval = 10

        guard let (data, _) = try? await URLSession.shared.data(for: request) else {
            Log.ui.warning("UpdateCheck: network request failed")
            return
        }

        guard let release = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
              let tagName = release["tag_name"] as? String else {
            Log.ui.warning("UpdateCheck: unexpected API response — \(String(data: data, encoding: .utf8) ?? "<binary>")")
            return
        }

        // Strip leading "v" from tag (e.g. "v2.3.1" → "2.3.1")
        let latestVer = tagName.hasPrefix("v") ? String(tagName.dropFirst()) : tagName
        let currentVer = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "0.0.0"

        // Extract direct DMG download URL from release assets
        let dmgURL: URL? = (release["assets"] as? [[String: Any]])?
            .first { ($0["name"] as? String ?? "").lowercased().hasSuffix(".dmg") }
            .flatMap { $0["browser_download_url"] as? String }
            .flatMap { URL(string: $0) }

        if isNewer(latestVer, than: currentVer) {
            await MainActor.run {
                self.latestVersion = latestVer
                self.downloadURL = dmgURL
                self.updateAvailable = true
            }
        }
    }

    /// Semantic version comparison — returns true if `version` is newer than `current`.
    private func isNewer(_ version: String, than current: String) -> Bool {
        let lhs = version.split(separator: ".").compactMap { Int($0) }
        let rhs = current.split(separator: ".").compactMap { Int($0) }
        let count = max(lhs.count, rhs.count)
        for i in 0..<count {
            let l = i < lhs.count ? lhs[i] : 0
            let r = i < rhs.count ? rhs[i] : 0
            if l > r { return true }
            if l < r { return false }
        }
        return false
    }

    /// Open the GitLab releases page in the default browser.
    func openReleasesPage() {
        #if os(macOS)
        NSWorkspace.shared.open(releasesURL)
        #endif
    }
}
