import Foundation

/// HTTP transport used by territory install/update: a synchronous GET with
/// `Authorization: Bearer <apiKey>` against the MapMap gateway, plus HTTP
/// status -> `BridgeError` classification. Kept separate from the SDK
/// callback adapter below so the transport logic is testable and
/// SDK-shape-independent.
///
/// The MapMap core owns *what* to fetch and verifies every byte's
/// signature/hash itself (see `TerritoryStore` docs); this transport just
/// moves bytes and never trusts them.
final class GatewayTransport: @unchecked Sendable {
  private let apiKey: String
  private let session: URLSession

  /// Called with (bytesDownloaded) as chunks arrive, for progress events.
  var onBytes: ((Int64) -> Void)?

  init(apiKey: String) {
    self.apiKey = apiKey
    let config = URLSessionConfiguration.default
    config.waitsForConnectivity = false
    config.timeoutIntervalForRequest = 60
    self.session = URLSession(configuration: config)
  }

  /// Synchronous GET returning the body, or throwing a classified
  /// `BridgeError`. Safe to call off the main thread (the SDK invokes its
  /// fetcher from a native background task).
  func get(_ urlString: String) throws -> Data {
    guard let url = URL(string: urlString) else {
      throw BridgeError(.network, "invalid layer URL: \(urlString)")
    }
    var request = URLRequest(url: url)
    request.httpMethod = "GET"
    request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")

    let semaphore = DispatchSemaphore(value: 0)
    var resultData: Data?
    var resultError: BridgeError?

    let task = session.dataTask(with: request) { data, response, error in
      defer { semaphore.signal() }
      if let error = error {
        resultError = BridgeError(.network, "transport: \(error.localizedDescription)")
        return
      }
      guard let http = response as? HTTPURLResponse else {
        resultError = BridgeError(.network, "no HTTP response for \(urlString)")
        return
      }
      switch http.statusCode {
      case 200...299:
        let body = data ?? Data()
        self.onBytes?(Int64(body.count))
        resultData = body
      case 401, 403:
        resultError = BridgeError(.auth, "gateway rejected the API key (HTTP \(http.statusCode))")
      case 402:
        resultError = BridgeError(
          .territoryAllowance, "download allowance exhausted (HTTP 402)")
      case 404:
        resultError = BridgeError(
          .territoryNotFound, "layer not found on gateway (HTTP 404)")
      default:
        resultError = BridgeError(.network, "gateway HTTP \(http.statusCode) for \(urlString)")
      }
    }
    task.resume()
    semaphore.wait()

    if let resultError = resultError { throw resultError }
    return resultData ?? Data()
  }
}

// MARK: - SDK adapter

/// Adapts `GatewayTransport` to the SDK's `LayerFetcher` callback protocol,
/// which `TerritoryStore.checkForUpdate(...)` / `applyUpdate(...)` invoke.
///
/// PROTOCOL SHAPE — now confirmed against the real (vendored) UniFFI
/// bindings (`Vendored/MapMapKit/Generated/mapmap.swift`):
///
///     func fetch(relPath: String, expectedBlake3: String, destPath: String) throws
///
/// The core hands us a channel-relative path and a local destination file,
/// downloads by resolving `relPath` against the gateway base URL and writes
/// the bytes to `destPath`. `expectedBlake3` is advisory — the core
/// re-verifies all content itself — so we ignore it here.
final class GatewayLayerFetcher: LayerFetcher, @unchecked Sendable {
  private let transport: GatewayTransport
  private let base: URL

  init(transport: GatewayTransport, base: URL) {
    self.transport = transport
    self.base = base
  }

  func fetch(relPath: String, expectedBlake3: String, destPath: String) throws {
    let urlString = base.appendingPathComponent(relPath).absoluteString
    let data = try transport.get(urlString)
    try data.write(to: URL(fileURLWithPath: destPath), options: .atomic)
  }
}
