import AVFoundation
import Foundation

// MARK: - DRMAdapter

/**
 * DRMAdapter — FairPlay DRM lifecycle as a CommandInterceptor.
 *
 * Owns the full FairPlay lifecycle: AVContentKeySession creation,
 * AVContentKeySessionDelegate, async certificate fetch, license request.
 * The decoder receives ready-to-play assets with zero DRM knowledge.
 *
 * Architecture:
 *   Pipeline dispatches CMD_DECODER_LOAD through this interceptor.
 *   For DRM content (FairPlay):
 *     1. Creates AVContentKeySession on a serial DRM queue (NOT main)
 *     2. Sets itself as delegate
 *     3. Stores session in LoadContext.contentKeySession
 *     4. Calls next() — key requests handled asynchronously by delegate
 *   For non-DRM content:
 *     Calls next() immediately (pass-through).
 *
 * Threading:
 *   Certificate and license network requests run on URLSession's background queue.
 *   AVContentKeySessionDelegate callbacks dispatch on drmQueue (serial).
 *   No DispatchSemaphore anywhere — fully async.
 *
 * Error Reporting:
 *   DRM errors are emitted directly through the C event bus with distinct codes:
 *   - DRM_CERTIFICATE_FAILED: certificate fetch failed after retry
 *   - DRM_LICENSE_FAILED: license server request failed after retry
 *   - DRM_KEY_EXPIRED: content key expired or was revoked
 */
class DRMAdapter: NSObject, CommandInterceptor, AVContentKeySessionDelegate {
    // MARK: - Private state

    /// Serial queue for thread-safe DRM operations (NOT main queue).
    private let drmQueue = DispatchQueue(label: "aviation.drm")

    /// Certificate cache: keyed by license URI, cleared on teardown.
    private var certificateCache: [String: Data] = [:]

    /// Pre-acquired sessions: keyed by content URI.
    private var preAcquiredSessions: [String: AVContentKeySession] = [:]

    /// Pending key request state: maps session → (licenseUri, headers).
    /// Needed because AVContentKeySessionDelegate callbacks don't carry this context.
    private var pendingDrmConfigs: [ObjectIdentifier: (licenseUri: String, headers: [String: String]?)] = [:]

    /// Event bus reference for error emission (set by bridge at init).
    private var eventBusPtr: OpaquePointer?

    /// C coordinator pointer for queue lookahead.
    private var coordinatorPtr: OpaquePointer?

    /// Closure to resolve queue index → HybridMediaItem (thread-safe: call on main only).
    typealias QueueItemProvider = (Int) -> (any HybridMediaItemSpec)?
    private var queueItemProvider: QueueItemProvider?

    /// Domain event subscription ID for teardown (0 = not subscribed).
    private var domainSubscriptionId: UInt32 = 0

    // MARK: - Initialization

    init(eventBus: OpaquePointer?, coordinator: OpaquePointer?, queueItemProvider: @escaping QueueItemProvider) {
        eventBusPtr = eventBus
        coordinatorPtr = coordinator
        self.queueItemProvider = queueItemProvider
        super.init()
        subscribeToDomainEvents()
    }

    // MARK: - CommandInterceptor

    func intercept(event _: AviationCommandEvent, context: LoadContext, next: @escaping () -> Void) {
        // Non-DRM content: pass through immediately
        guard context.hasDrm,
              context.nativeItem?.drmType == .fairplay,
              let licenseUri = context.nativeItem?.drmLicenseUri
        else {
            next()
            return
        }

        // Check for pre-acquired session
        if let preAcquired = preAcquiredSessions.removeValue(forKey: context.uri) {
            context.contentKeySession = preAcquired
            next()
            return
        }

        // Create new AVContentKeySession on drmQueue (NOT main)
        let keySession = AVContentKeySession(keySystem: .fairPlayStreaming)
        keySession.setDelegate(self, queue: drmQueue)
        context.contentKeySession = keySession

        // Store DRM config for delegate callbacks
        let sessionKey = ObjectIdentifier(keySession)
        pendingDrmConfigs[sessionKey] = (licenseUri: licenseUri, headers: context.nativeItem?.drmHeaders)

        // Continue chain — key requests will be handled asynchronously by the delegate
        next()
    }

    // MARK: - AVContentKeySessionDelegate

    /// Called when the system needs a content key for a FairPlay-protected resource.
    func contentKeySession(_ session: AVContentKeySession,
                           didProvide keyRequest: AVContentKeyRequest)
    {
        let sessionKey = ObjectIdentifier(session)
        guard let config = pendingDrmConfigs[sessionKey],
              let licenseUrl = URL(string: config.licenseUri)
        else {
            keyRequest.processContentKeyResponseError(
                NSError(domain: "Aviation", code: -1,
                        userInfo: [NSLocalizedDescriptionKey: "Missing DRM license URI"]))
            emitDrmError(message: "Missing DRM license URI for key request", code: "DRM_LICENSE_FAILED")
            return
        }

        // Extract the content identifier from the key request
        guard let contentIdentifierData = (keyRequest.identifier as? String)?
            .replacingOccurrences(of: "skd://", with: "")
            .data(using: .utf8)
        else {
            keyRequest.processContentKeyResponseError(
                NSError(domain: "Aviation", code: -2,
                        userInfo: [NSLocalizedDescriptionKey: "Invalid content identifier"]))
            emitDrmError(message: "Invalid FairPlay content identifier", code: "DRM_LICENSE_FAILED")
            return
        }

        // Async certificate fetch (no DispatchSemaphore)
        fetchCertificate(from: licenseUrl, headers: config.headers) { [weak self] result in
            switch result {
            case let .success(certificateData):
                keyRequest.makeStreamingContentKeyRequestData(
                    forApp: certificateData,
                    contentIdentifier: contentIdentifierData
                ) { [weak self] spcData, error in
                    guard let spcData else {
                        keyRequest.processContentKeyResponseError(
                            error ?? NSError(domain: "Aviation", code: -3,
                                             userInfo: [NSLocalizedDescriptionKey: "Failed to create SPC data"]))
                        self?.emitDrmError(message: "Failed to create SPC data: \(error?.localizedDescription ?? "unknown")",
                                           code: "DRM_LICENSE_FAILED")
                        return
                    }
                    self?.requestLicense(spc: spcData, from: licenseUrl, headers: config.headers) { ckcData, licError in
                        if let ckcData {
                            let response = AVContentKeyResponse(fairPlayStreamingKeyResponseData: ckcData)
                            keyRequest.processContentKeyResponse(response)
                        } else {
                            keyRequest.processContentKeyResponseError(
                                licError ?? NSError(domain: "Aviation", code: -4,
                                                    userInfo: [NSLocalizedDescriptionKey: "Failed to obtain license"]))
                            self?.emitDrmError(message: "License request failed: \(licError?.localizedDescription ?? "unknown")",
                                               code: "DRM_LICENSE_FAILED")
                        }
                    }
                }

            case let .failure(error):
                keyRequest.processContentKeyResponseError(error)
                self?.emitDrmError(message: "Certificate fetch failed: \(error.localizedDescription)",
                                   code: "DRM_CERTIFICATE_FAILED")
            }
        }
    }

    /// Called when a content key request failed.
    func contentKeySession(_: AVContentKeySession,
                           contentKeyRequest _: AVContentKeyRequest,
                           didFailWithError err: Error)
    {
        emitDrmError(message: "FairPlay key request failed: \(err.localizedDescription)", code: "DRM_KEY_EXPIRED")
    }

    // MARK: - Async Certificate Fetch (replaces DispatchSemaphore pattern)

    /// Fetch FairPlay server certificate asynchronously with caching and 1-retry backoff.
    private func fetchCertificate(from url: URL, headers: [String: String]?,
                                  completion: @escaping (Result<Data, Error>) -> Void)
    {
        // Check cache
        if let cached = certificateCache[url.absoluteString] {
            completion(.success(cached))
            return
        }

        var request = URLRequest(url: url, timeoutInterval: 30)
        request.httpMethod = "GET"
        headers?.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }

        URLSession.shared.dataTask(with: request) { [weak self] data, _, error in
            if error != nil {
                // Retry once with exponential backoff (1 second delay)
                DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
                    self?.fetchCertificateRetry(from: url, headers: headers, completion: completion)
                }
                return
            }
            guard let data, !data.isEmpty else {
                completion(.failure(NSError(domain: "Aviation", code: -5,
                                            userInfo: [NSLocalizedDescriptionKey: "Empty certificate response"])))
                return
            }
            self?.certificateCache[url.absoluteString] = data
            completion(.success(data))
        }.resume()
    }

    /// Single retry for certificate fetch with 2-second backoff.
    private func fetchCertificateRetry(from url: URL, headers: [String: String]?,
                                       completion: @escaping (Result<Data, Error>) -> Void)
    {
        var request = URLRequest(url: url, timeoutInterval: 30)
        request.httpMethod = "GET"
        headers?.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }

        URLSession.shared.dataTask(with: request) { [weak self] data, _, error in
            if let error {
                completion(.failure(error))
                return
            }
            guard let data, !data.isEmpty else {
                completion(.failure(NSError(domain: "Aviation", code: -5,
                                            userInfo: [NSLocalizedDescriptionKey: "Empty certificate response on retry"])))
                return
            }
            self?.certificateCache[url.absoluteString] = data
            completion(.success(data))
        }.resume()
    }

    // MARK: - License Request

    /// Send the Server Playback Context (SPC) to the license server and receive
    /// the Content Key Context (CKC) response. 30-second timeout, 1-retry with backoff.
    private func requestLicense(spc: Data, from licenseUrl: URL,
                                headers: [String: String]?,
                                completion: @escaping (Data?, Error?) -> Void)
    {
        var request = URLRequest(url: licenseUrl, timeoutInterval: 30)
        request.httpMethod = "POST"
        request.httpBody = spc
        request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
        headers?.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }

        URLSession.shared.dataTask(with: request) { [weak self] data, _, error in
            if error != nil {
                // Retry once with 2-second backoff
                DispatchQueue.global().asyncAfter(deadline: .now() + 2.0) {
                    self?.requestLicenseRetry(spc: spc, from: licenseUrl, headers: headers, completion: completion)
                }
                return
            }
            guard let data, !data.isEmpty else {
                completion(nil, NSError(domain: "Aviation", code: -4,
                                        userInfo: [NSLocalizedDescriptionKey: "Empty CKC response from license server"]))
                return
            }
            completion(data, nil)
        }.resume()
    }

    /// Single retry for license request.
    private func requestLicenseRetry(spc: Data, from licenseUrl: URL,
                                     headers: [String: String]?,
                                     completion: @escaping (Data?, Error?) -> Void)
    {
        var request = URLRequest(url: licenseUrl, timeoutInterval: 30)
        request.httpMethod = "POST"
        request.httpBody = spc
        request.setValue("application/octet-stream", forHTTPHeaderField: "Content-Type")
        headers?.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }

        URLSession.shared.dataTask(with: request) { data, _, error in
            if let error {
                completion(nil, error)
                return
            }
            guard let data, !data.isEmpty else {
                completion(nil, NSError(domain: "Aviation", code: -4,
                                        userInfo: [NSLocalizedDescriptionKey: "Empty CKC response from license server on retry"]))
                return
            }
            completion(data, nil)
        }.resume()
    }

    // MARK: - Error Emission via C Event Bus

    /// Emit a DRM error through the C event bus with a distinct error code.
    private func emitDrmError(message: String, code: String) {
        guard let bus = eventBusPtr else { return }
        var event = AviationDomainEvent()
        event.type = Int32(AVIATION_EVT_ERROR_OCCURRED.rawValue)

        // Copy message into fixed-size C char array
        message.withCString { msgPtr in
            withUnsafeMutablePointer(to: &event.errorMessage) { dest in
                _ = strncpy(
                    UnsafeMutableRawPointer(dest).assumingMemoryBound(to: CChar.self),
                    msgPtr,
                    Int(AVIATION_MAX_ERROR_LEN) - 1
                )
            }
        }

        // Copy code into fixed-size C char array
        code.withCString { codePtr in
            withUnsafeMutablePointer(to: &event.errorCode) { dest in
                _ = strncpy(
                    UnsafeMutableRawPointer(dest).assumingMemoryBound(to: CChar.self),
                    codePtr,
                    Int(AVIATION_MAX_ERROR_CODE_LEN) - 1
                )
            }
        }

        aviation_event_bus_emit_domain(bus, &event)
    }

    // MARK: - Domain Event Subscription (pre-acquisition)

    /// Subscribe to C event bus domain events for DRM pre-acquisition.
    private func subscribeToDomainEvents() {
        guard let bus = eventBusPtr else { return }

        let selfPtr = Unmanaged.passUnretained(self).toOpaque()
        domainSubscriptionId = aviation_event_bus_subscribe_domain(
            bus,
            { event, userData in
                guard let event, let userData else { return }
                let adapter = Unmanaged<DRMAdapter>.fromOpaque(userData).takeUnretainedValue()
                let eventCopy = event.pointee
                // Dispatch to DRM queue (NOT main) for thread safety
                adapter.drmQueue.async { [weak adapter] in
                    adapter?.handleDomainEvent(eventCopy)
                }
            },
            selfPtr
        )
    }

    /// Handle domain events — react to item change and queue mutations.
    private func handleDomainEvent(_ event: AviationDomainEvent) {
        let eventType = event.type
        guard eventType == Int32(AVIATION_EVT_CURRENT_ITEM_CHANGED.rawValue) ||
            eventType == Int32(AVIATION_EVT_QUEUE_ITEM_CHANGED.rawValue)
        else {
            return
        }
        preAcquireNextItem()
    }

    /// Pre-acquire DRM key session for the next queue item (1 item ahead).
    /// Uses the C coordinator to resolve the next index (shuffle/repeat aware).
    private func preAcquireNextItem() {
        guard let coord = coordinatorPtr, let provider = queueItemProvider else { return }

        // Resolve next 1 item using C queue API (respects shuffle/repeat)
        var nextIndex: Int32 = -1
        let count = aviation_coordinator_queue_resolve_next_n(coord, &nextIndex, 1)
        guard count > 0, nextIndex >= 0 else { return }

        // Look up the native item via the provider closure (must dispatch to main
        // for thread-safe access to _queueItemRefs)
        DispatchQueue.main.async { [weak self] in
            guard let self else { return }
            guard let item = provider(Int(nextIndex)) as? HybridMediaItem else { return }

            // Only pre-acquire for FairPlay DRM content
            guard item.hasDrm,
                  item.drmType == .fairplay,
                  let licenseUri = item.drmLicenseUri else { return }
            let uri = item.uri

            // Dispatch back to DRM queue for session creation
            drmQueue.async { [weak self] in
                guard let self else { return }

                // Already pre-acquired for this URI?
                if preAcquiredSessions[uri] != nil { return }

                // Create pre-acquisition session
                let keySession = AVContentKeySession(keySystem: .fairPlayStreaming)
                keySession.setDelegate(self, queue: drmQueue)

                // Store config for delegate callbacks
                let sessionKey = ObjectIdentifier(keySession)
                pendingDrmConfigs[sessionKey] = (licenseUri: licenseUri, headers: item.drmHeaders)

                // Store as pre-acquired (will be consumed by intercept() on next load)
                preAcquiredSessions[uri] = keySession

                #if DEBUG
                    NSLog("[Aviation] DRM pre-acquired session for next item: %@", uri)
                #endif
            }
        }
    }

    // MARK: - Teardown

    /// Clean up all DRM state. Called by engine on destroy.
    func teardown() {
        // Unsubscribe from domain events
        if domainSubscriptionId > 0, let bus = eventBusPtr {
            aviation_event_bus_unsubscribe_domain(bus, domainSubscriptionId)
            domainSubscriptionId = 0
        }
        preAcquiredSessions.removeAll()
        certificateCache.removeAll()
        pendingDrmConfigs.removeAll()
        queueItemProvider = nil
        coordinatorPtr = nil
    }
}
