//
// Copyright (c) Double Symmetry GmbH
// Commercial use requires a license. See https://rntp.dev/pricing
//

import XCTest
@testable import PlayerCore

final class DownloadCoordinatorTests: XCTestCase {
  private var cacheDir: URL!
  private var cache: AudioCache!
  private var coordinator: DownloadCoordinator!

  override func setUp() {
    super.setUp()
    cacheDir = FileManager.default.temporaryDirectory
      .appendingPathComponent("DownloadCoordinatorTests-\(UUID().uuidString)", isDirectory: true)
    cache = AudioCache(maxSizeBytes: 10 * 1024 * 1024, directory: cacheDir)
    coordinator = DownloadCoordinator(cache: cache)
  }

  override func tearDown() {
    coordinator = nil
    try? FileManager.default.removeItem(at: cacheDir)
    super.tearDown()
  }

  func testDownloadCachesDataAndFinalizesContentLength() throws {
    let testData = Data(repeating: 0xAB, count: 1000)
    let server = try LocalAudioServer(
      audioData: testData,
      options: .init(includeContentLength: true, contentType: "audio/mpeg")
    )
    server.start()
    defer { server.stop() }

    let url = server.url
    let key = cache.cacheKey(for: url)
    let done = expectation(description: "download complete")

    coordinator.download(url: url, headers: nil) { result in
      if case .success = result { done.fulfill() }
    }

    wait(for: [done], timeout: 5.0)

    XCTAssertEqual(cache.cachedBytes(for: key), Int64(testData.count))
    let info = cache.contentInfo(for: key)
    XCTAssertNotNil(info)
    XCTAssertEqual(info?.contentLength, Int64(testData.count))
  }

  func testDownloadWithoutContentLengthFinalizesOnCompletion() throws {
    let testData = Data(repeating: 0xCD, count: 500)
    let server = try LocalAudioServer(
      audioData: testData,
      options: .init(includeContentLength: false, contentType: "audio/aac")
    )
    server.start()
    defer { server.stop() }

    let url = server.url
    let key = cache.cacheKey(for: url)
    let done = expectation(description: "download complete")

    coordinator.download(url: url, headers: nil) { result in
      if case .success = result { done.fulfill() }
    }

    wait(for: [done], timeout: 5.0)

    XCTAssertEqual(cache.cachedBytes(for: key), Int64(testData.count))
    let info = cache.contentInfo(for: key)
    XCTAssertNotNil(info)
    XCTAssertEqual(info?.contentLength, Int64(testData.count),
                   "Content length should be finalized to actual byte count")
  }

  func testDuplicateDownloadReusesActiveTask() throws {
    let testData = Data(repeating: 0xEF, count: 2000)
    let server = try LocalAudioServer(
      audioData: testData,
      options: .init(includeContentLength: true, contentType: "audio/mpeg")
    )
    server.start()
    defer { server.stop() }

    let url = server.url
    let key = cache.cacheKey(for: url)

    let done1 = expectation(description: "first subscriber done")
    let done2 = expectation(description: "second subscriber done")

    coordinator.download(url: url, headers: nil) { _ in done1.fulfill() }
    coordinator.download(url: url, headers: nil) { _ in done2.fulfill() }

    wait(for: [done1, done2], timeout: 5.0)

    // Data should only be cached once (not duplicated)
    XCTAssertEqual(cache.cachedBytes(for: key), Int64(testData.count))
  }

  func testCancellingOneSubscriberDoesNotStopDownload() throws {
    let testData = Data(repeating: 0x11, count: 1000)
    let server = try LocalAudioServer(
      audioData: testData,
      options: .init(includeContentLength: true, contentType: "audio/mpeg")
    )
    server.start()
    defer { server.stop() }

    let url = server.url
    let key = cache.cacheKey(for: url)

    let done = expectation(description: "second subscriber done")

    let token1 = coordinator.download(url: url, headers: nil) { _ in }
    coordinator.download(url: url, headers: nil) { _ in done.fulfill() }
    coordinator.cancelDownload(token: token1)

    wait(for: [done], timeout: 5.0)
    XCTAssertEqual(cache.cachedBytes(for: key), Int64(testData.count))
  }

  func testCancellingAllSubscribersCancelsDownload() throws {
    let testData = Data(repeating: 0x22, count: 1000)
    let server = try LocalAudioServer(
      audioData: testData,
      options: .init(includeContentLength: true, contentType: "audio/mpeg")
    )
    server.start()
    defer { server.stop() }

    let url = server.url

    let token1 = coordinator.download(url: url, headers: nil) { _ in }
    let token2 = coordinator.download(url: url, headers: nil) { _ in }
    coordinator.cancelDownload(token: token1)
    coordinator.cancelDownload(token: token2)

    // Give time for cancellation to propagate
    Thread.sleep(forTimeInterval: 0.5)

    XCTAssertTrue(coordinator.activeDownloadCount == 0)
  }

  func testDownloadStopsAtMaxBytes() throws {
    // Use 200 KB so the slow-network path sends multiple 64 KB chunks,
    // giving the coordinator a chance to cancel mid-stream.
    let totalBytes = 200 * 1024
    let testData = Data(repeating: 0xBB, count: totalBytes)
    let server = try LocalAudioServer(
      audioData: testData,
      options: .init(includeContentLength: true, contentType: "audio/mpeg", simulateSlowNetwork: true)
    )
    server.start()
    defer { server.stop() }

    let url = server.url
    let key = cache.cacheKey(for: url)
    // Stop after ~70 KB — well into the second 64 KB chunk but short of the full 200 KB.
    let maxBytes: Int64 = 70 * 1024
    let done = expectation(description: "download stopped at limit")

    coordinator.download(url: url, headers: nil, cacheKey: key, maxBytes: maxBytes) { result in
      if case .success = result { done.fulfill() }
    }

    wait(for: [done], timeout: 10.0)

    let cached = cache.cachedBytes(for: key)
    // Should be at least maxBytes but not the full file.
    // The coordinator checks after each data chunk, so it may slightly overshoot.
    XCTAssertGreaterThanOrEqual(cached, maxBytes)
    XCTAssertLessThan(cached, Int64(testData.count),
                      "Should not have downloaded the full file")
  }

  func testFullyCachedURLSkipsDownload() throws {
    let testData = Data(repeating: 0x33, count: 100)
    let url = URL(string: "https://example.com/cached.mp3")!
    let key = cache.cacheKey(for: url)

    // Pre-populate cache
    let info = AudioCache.ContentInfo(contentType: "audio/mpeg", contentLength: 100, isByteRangeAccessSupported: false)
    cache.storeContentInfo(info, for: key, url: url)
    cache.appendData(testData, for: key)

    let done = expectation(description: "immediate completion")
    coordinator.download(url: url, headers: nil) { result in
      if case .success = result { done.fulfill() }
    }

    wait(for: [done], timeout: 1.0)
    XCTAssertEqual(coordinator.activeDownloadCount, 0, "No download should have started")
  }
}
