import Foundation
import AppUpdater
import Version

/// A custom `ReleaseProvider` that uses tolerant version decoding so that
/// GitHub release tags prefixed with "v" (e.g. "v1.36.0") are accepted.
///
/// The default `GithubReleaseProvider` uses strict `Version` decoding which
/// rejects the "v" prefix. Setting `DecodingMethod.tolerant` in the decoder's
/// `userInfo` causes `Version.init?(tolerant:)` to be used instead, which
/// strips the prefix before parsing.
///
/// If a `GH_TOKEN` environment variable is set, the request is authenticated
/// with `Authorization: Bearer <token>`. GitHub's unauthenticated API limit
/// is 60 requests/hour per IP and is hit easily by users behind corporate
/// NAT or shared CI runners; an authenticated request gets 5,000/hour. The
/// provider falls back to anonymous requests when no token is present so
/// regular users — who do not need to set anything — keep working.
struct TolerantGithubReleaseProvider: ReleaseProvider {
    private let github = GithubReleaseProvider()
    private let authToken: String?
    private let host: UpdateHostConfiguration

    init(
        authToken: String? = nil,
        host: UpdateHostConfiguration = UpdateHostConfiguration.resolve()
    ) {
        // Treat an empty `GH_TOKEN` (e.g. `export GH_TOKEN=` from a script
        // that forgot to populate it) as no token. Otherwise we would emit
        // `Authorization: Bearer ` and GitHub would 401 with a misleading
        // `URLError(.badServerResponse)` at the call site.
        let resolved = authToken ?? ProcessInfo.processInfo.environment["GH_TOKEN"]
        self.authToken = resolved.flatMap { $0.isEmpty ? nil : $0 }
        self.host = host
    }

    func fetchReleases(owner: String, repo: String, proxy: URLRequestProxy?) async throws -> [Release] {
        let url = host.releasesURL(owner: owner, repo: repo)
        var request = URLRequest(url: url)
        if let authToken {
            request.setValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization")
        }
        request = request.applyOrOriginal(proxy: proxy)
        let (data, response) = try await URLSession.shared.data(for: request)
        guard let httpResponse = response as? HTTPURLResponse, (200..<300).contains(httpResponse.statusCode) else {
            throw URLError(.badServerResponse)
        }
        let decoder = JSONDecoder()
        decoder.userInfo[.decodingMethod] = DecodingMethod.tolerant
        return try decoder.decode([Release].self, from: data)
    }

    func download(asset: Release.Asset, to saveLocation: URL, proxy: URLRequestProxy?) async throws -> AsyncThrowingStream<DownloadingState, Error> {
        try await github.download(asset: asset, to: saveLocation, proxy: proxy)
    }

    func fetchAssetData(asset: Release.Asset, proxy: URLRequestProxy?) async throws -> Data {
        try await github.fetchAssetData(asset: asset, proxy: proxy)
    }
}

