import CoreGraphics
import Foundation
import ImageCaptureCore

/// Canon 相机 ImageCaptureCore 设备管理：会话、GetEvent 轮询与 PTP/Catalog 双路径下载。
final class CanonImageCaptureDeviceManager: NSObject {
    var onConnectionStateChange: ((CameraConnectionState) -> Void)?
    var onNewItems: (([ICCameraItem]) -> Void)?
    var onBaselineCatalogReady: (() -> Void)?
    var onPollTick: (() -> Void)?
    var onPTPAssetReady: ((CameraAsset, URL) -> Void)?
    var onError: ((String) -> Void)?

    private(set) var cameraDevice: ICCameraDevice?
    private(set) var isCatalogReady = false
    private var deviceBrowser: ICDeviceBrowser?
    private let downloadDelegate = CameraDownloadDelegate()
    private var transactionID: UInt32 = 1
    private var pendingDownloads: [String: CheckedContinuation<URL, Error>] = [:]
    private var ptpAssetURLs: [String: URL] = [:]
    private var pollTimer: Timer?
    private var baselineNotified = false
    private var isRemoteSessionReady = false
    private var isExternalAttach = false
    private var eventPollTask: Task<Void, Never>?
    private var keepDeviceOnSupported = false
    private var eventPollTick = 0
    private var seenHandles: Set<UInt32> = []
    private var isDownloadingPTPObject = false
    private var sessionSetupTask: Task<Void, Never>?
    private var sessionCloseContinuation: CheckedContinuation<Void, Never>?
    private var sessionCloseTimeoutTask: Task<Void, Never>?
    private var closingDeviceUUID: String?
    private var isDisconnecting = false

    var isBrowsing: Bool { deviceBrowser != nil || isExternalAttach }

    /// 跳过 Browser，直接连接 AutoDetect 已发现并授权的设备。
    func attach(to camera: ICCameraDevice) {
        guard cameraDevice == nil else {
            CameraTetheringLogger.log("Canon attach: 已有连接中的相机，忽略")
            return
        }
        isExternalAttach = true
        CameraTetheringLogger.log("Canon attach: \(camera.name ?? "Unknown")")
        openSession(for: camera)
    }

    func startBrowsing() {
        guard deviceBrowser == nil else {
            CameraTetheringLogger.log("Canon: 已在监听 USB 相机")
            if cameraDevice == nil {
                updateState(.searching)
            }
            return
        }

        CameraTetheringLogger.log("Canon: 开始搜索 USB 相机…")
        updateState(.searching)

        let browser = ICDeviceBrowser()
        browser.delegate = self
        if #available(iOS 15.2, *) {
            let mask = ICDeviceTypeMask(
                rawValue: ICDeviceTypeMask.camera.rawValue | ICDeviceLocationTypeMask.local.rawValue
            ) ?? .camera
            browser.browsedDeviceTypeMask = mask
        }
        deviceBrowser = browser
        browser.start()
    }

    func stopBrowsing() async {
        CameraTetheringLogger.log("Canon: 停止搜索并断开")
        stopEventPolling()
        stopPolling()
        await disconnect()
        if !isExternalAttach {
            deviceBrowser?.stop()
        }
        deviceBrowser = nil
        isExternalAttach = false
        isCatalogReady = false
        baselineNotified = false
        updateState(.disconnected)
    }

    func disconnect() async {
        guard !isDisconnecting else { return }
        isDisconnecting = true
        defer { isDisconnecting = false }

        sessionSetupTask?.cancel()
        sessionSetupTask = nil

        let shouldClosePTP = isRemoteSessionReady
        isRemoteSessionReady = false
        isDownloadingPTPObject = false

        stopEventPolling()
        stopPolling()
        cancelPendingDownloads()

        guard let camera = cameraDevice else {
            resetDisconnectLocalState()
            return
        }

        let deviceUUID = camera.uuidString ?? ""
        camera.ptpEventHandler = { _ in }

        if shouldClosePTP {
            var localTransactionID = transactionID
            await CanonPTPHelper.closeRemoteSession(on: camera, transactionID: &localTransactionID)
        }

        await waitForSessionClose(on: camera, deviceUUID: deviceUUID)
        camera.delegate = nil
        resetDisconnectLocalState()
    }

    private func cancelPendingDownloads() {
        let disconnected = CameraServiceError.notConnected
        for (_, continuation) in pendingDownloads {
            continuation.resume(throwing: disconnected)
        }
        pendingDownloads.removeAll()
    }

    private func waitForSessionClose(on camera: ICCameraDevice, deviceUUID: String) async {
        sessionCloseTimeoutTask?.cancel()
        await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
            sessionCloseContinuation = continuation
            closingDeviceUUID = deviceUUID
            camera.requestCloseSession()

            sessionCloseTimeoutTask = Task { [weak self] in
                try? await Task.sleep(nanoseconds: 3_000_000_000)
                guard let self, self.sessionCloseContinuation != nil else { return }
                CameraTetheringLogger.log("Canon 会话关闭超时，继续 teardown")
                self.finishSessionClose()
            }
        }
        sessionCloseTimeoutTask?.cancel()
        sessionCloseTimeoutTask = nil
    }

    private func finishSessionClose() {
        sessionCloseContinuation?.resume()
        sessionCloseContinuation = nil
        closingDeviceUUID = nil
    }

    private func resetDisconnectLocalState() {
        cameraDevice = nil
        transactionID = 1
        isCatalogReady = false
        baselineNotified = false
        isRemoteSessionReady = false
        keepDeviceOnSupported = false
        eventPollTick = 0
        seenHandles.removeAll()
        ptpAssetURLs.removeAll()
        isExternalAttach = false
    }

    func localURL(forAssetID assetID: String) -> URL? {
        ptpAssetURLs[assetID]
    }

    func allFiles() -> [ICCameraFile] {
        guard let camera = cameraDevice else { return [] }
        if let mediaFiles = camera.mediaFiles, !mediaFiles.isEmpty {
            return mediaFiles.compactMap { $0 as? ICCameraFile }
        }
        return collectFiles(from: camera.contents ?? [])
    }

    func file(forAssetID assetID: String) -> ICCameraFile? {
        allFiles().first { assetIdentifier(for: $0) == assetID }
    }

    func download(file: ICCameraFile, to directory: URL) async throws -> URL {
        guard let camera = cameraDevice else {
            throw CameraServiceError.notConnected
        }

        let filename = file.name ?? "photo_\(UUID().uuidString).jpg"
        let assetID = assetIdentifier(for: file)

        CameraTetheringLogger.log("Canon 开始下载 \(filename), handle=\(file.ptpObjectHandle)", logger: CameraTetheringLogger.transfer)

        return try await withCheckedThrowingContinuation { continuation in
            pendingDownloads[assetID] = continuation

            downloadDelegate.onComplete = { [weak self] completedFile, url, error in
                guard let self else { return }
                let completedID = self.assetIdentifier(for: completedFile)
                guard let pending = self.pendingDownloads.removeValue(forKey: completedID) else { return }

                if let error {
                    pending.resume(throwing: error)
                } else if let url {
                    pending.resume(returning: url)
                } else {
                    pending.resume(throwing: CameraServiceError.downloadFailed("未知错误"))
                }
            }

            let options: [ICDownloadOption: Any] = [
                .downloadsDirectoryURL: directory,
                .savedFilename: filename,
                .overwrite: true
            ]

            camera.requestDownloadFile(
                file,
                options: options,
                downloadDelegate: downloadDelegate,
                didDownloadSelector: #selector(CameraDownloadDelegate.didDownloadFile(_:error:options:contextInfo:)),
                contextInfo: nil
            )
        }
    }

    func assetIdentifier(for file: ICCameraFile) -> String {
        if file.ptpObjectHandle != 0 {
            return "ptp_\(file.ptpObjectHandle)"
        }
        if let persistentID = file.value(forKey: "persistentID") as? String, !persistentID.isEmpty {
            return persistentID
        }
        let name = file.name ?? "unknown"
        let size = file.fileSize
        let date = file.creationDate?.timeIntervalSince1970 ?? 0
        return "\(name)_\(size)_\(Int(date))"
    }

    func makeAsset(from file: ICCameraFile) -> CameraAsset {
        let filename = file.name ?? "unknown"
        let isMovie = file.duration > 0 || Self.movieExtensions.contains(where: { filename.lowercased().hasSuffix($0) })
        return CameraAsset(
            id: assetIdentifier(for: file),
            filename: filename,
            fileSize: file.fileSize,
            creationDate: file.creationDate,
            isMovie: isMovie
        )
    }

    private static let movieExtensions = [".mp4", ".mov", ".m4v", ".avi"]

    private func collectFiles(from items: [ICCameraItem]) -> [ICCameraFile] {
        var files: [ICCameraFile] = []
        for item in items {
            if let file = item as? ICCameraFile {
                files.append(file)
            } else if let folder = item as? ICCameraFolder {
                files.append(contentsOf: collectFiles(from: folder.contents ?? []))
            }
        }
        return files
    }

    private func updateState(_ state: CameraConnectionState) {
        CameraTetheringLogger.log("Canon 连接状态: \(state.displayText)")
        onConnectionStateChange?(state)
    }

    private func requestAuthorizations(completion: @escaping (Bool, Bool) -> Void) {
        guard let browser = deviceBrowser else {
            completion(false, false)
            return
        }

        var contentsGranted = false
        var controlGranted = false
        let group = DispatchGroup()

        group.enter()
        browser.requestContentsAuthorization { status in
            contentsGranted = status == .authorized
            group.leave()
        }

        group.enter()
        browser.requestControlAuthorization { status in
            controlGranted = status == .authorized
            group.leave()
        }

        group.notify(queue: .main) {
            completion(contentsGranted, controlGranted)
        }
    }

    private func openSession(for camera: ICCameraDevice) {
        updateState(.connecting)
        camera.delegate = self
        cameraDevice = camera
        CameraTetheringLogger.log("Canon 打开相机会务: \(camera.name ?? "Unknown")")
        camera.requestOpenSession(options: nil) { [weak self] error in
            guard let self else { return }
            if let error {
                CameraTetheringLogger.log("Canon 会话打开失败: \(error.localizedDescription)")
                self.onError?(error.localizedDescription)
                self.updateState(.error(error.localizedDescription))
                return
            }
            let name = camera.name ?? "Canon Camera"
            CameraTetheringLogger.log("Canon 会话已打开: \(name)")
            self.updateState(.connected(deviceName: name))
            self.configureCameraAfterSession(camera)
        }
    }

    private func configureCameraAfterSession(_ camera: ICCameraDevice) {
        let capabilities = camera.capabilities.joined(separator: ", ")
        CameraTetheringLogger.log("Canon 相机能力: [\(capabilities)]")

        camera.ptpEventHandler = { [weak self] eventData in
            self?.handlePTPEvent(eventData)
        }

        sessionSetupTask = Task { [weak self] in
            guard let self else { return }
            let session = await CanonPTPHelper.initializeRemoteSession(
                on: camera,
                startingTransactionID: transactionID
            )
            guard !Task.isCancelled, !self.isDisconnecting else { return }
            transactionID = session.transactionID
            keepDeviceOnSupported = session.keepDeviceOnSupported
            isRemoteSessionReady = true
            startEventPolling()
        }
    }

    private func handlePTPEvent(_ eventData: Data) {
        guard !isDisconnecting else { return }
        let hex = eventData.prefix(16).map { String(format: "%02X", $0) }.joined(separator: " ")
        CameraTetheringLogger.log("Canon 收到 PTP 事件: \(hex)", logger: CameraTetheringLogger.ptp)
    }

    private func startEventPolling() {
        stopEventPolling()
        CameraTetheringLogger.log("Canon: 启动 GetEvent 轮询 (每 \(CanonPTPCommand.getEventInterval)s)", logger: CameraTetheringLogger.ptp)

        eventPollTask = Task { [weak self] in
            while !Task.isCancelled {
                guard let self, !self.isDisconnecting else { return }
                await self.pollEventsOnce()
                try? await Task.sleep(nanoseconds: UInt64(CanonPTPCommand.getEventInterval * 1_000_000_000))
            }
        }
    }

    private func stopEventPolling() {
        eventPollTask?.cancel()
        eventPollTask = nil
    }

    private func pollEventsOnce() async {
        guard !isDisconnecting, isRemoteSessionReady, let camera = cameraDevice else { return }

        eventPollTick += 1
        if keepDeviceOnSupported, eventPollTick % CanonPTPCommand.keepOnIntervalTicks == 0 {
            var localID = transactionID
            _ = await PTPHelper.sendCommand(
                on: camera,
                code: CanonPTPCommand.eosKeepDeviceOn,
                transactionID: &localID,
                parameters: [],
                outData: nil,
                label: "KeepDeviceOn(poll)"
            )
            transactionID = localID
        }

        var localID = transactionID
        let blob = await CanonPTPHelper.pollEvents(on: camera, transactionID: &localID)
        transactionID = localID

        guard !blob.isEmpty else { return }

        let parsed = CanonEosEventParser.parse(blob)
        let addedCount = parsed.filter {
            if case .objectAdded = $0 { return true }
            return false
        }.count

        if addedCount > 0 {
            CameraTetheringLogger.log("Canon GetEvent: \(parsed.count) 事件, 新增 \(addedCount)", logger: CameraTetheringLogger.ptp)
        }

        for event in parsed {
            switch event {
            case .objectAdded(let added):
                await handleObjectAdded(added)
            case .requestObjectTransfer(let handle):
                CameraTetheringLogger.log("Canon RequestObjectTransfer handle=\(handle)", logger: CameraTetheringLogger.ptp)
            case .cameraStatus:
                break
            }
        }
    }

    private func handleObjectAdded(_ added: CanonEosEventParser.ObjectAdded) async {
        guard !isDisconnecting else { return }
        guard seenHandles.insert(added.objectHandle).inserted else { return }

        let asset = CanonEosEventParser.makeAsset(from: added)
        guard CanonPTPHelper.isSupportedFilename(asset.filename) else {
            CameraTetheringLogger.log("Canon: 跳过不支持的文件 \(asset.filename)", logger: CameraTetheringLogger.transfer)
            return
        }

        guard !isDownloadingPTPObject else {
            CameraTetheringLogger.log("Canon: 下载进行中，稍后重试 \(asset.filename)", logger: CameraTetheringLogger.transfer)
            seenHandles.remove(added.objectHandle)
            return
        }

        isDownloadingPTPObject = true
        defer { isDownloadingPTPObject = false }

        guard let camera = cameraDevice else { return }

        var localID = transactionID
        guard let data = await CanonPTPHelper.downloadObject(
            handle: added.objectHandle,
            on: camera,
            transactionID: &localID
        ) else {
            seenHandles.remove(added.objectHandle)
            CameraTetheringLogger.log("Canon: PTP 下载失败 \(asset.filename)", logger: CameraTetheringLogger.transfer)
            return
        }
        transactionID = localID

        await CanonPTPHelper.transferComplete(
            handle: added.objectHandle,
            on: camera,
            transactionID: &localID
        )
        transactionID = localID

        let destination = CameraDownloadStorage.uniqueTemporaryURL(for: asset.filename)
        do {
            try data.write(to: destination, options: .atomic)
        } catch {
            seenHandles.remove(added.objectHandle)
            CameraTetheringLogger.log("Canon: 文件写入失败 \(error.localizedDescription)", logger: CameraTetheringLogger.transfer)
            return
        }

        ptpAssetURLs[asset.id] = destination
        CameraTetheringLogger.log(
            "Canon PTP 下载完成: \(asset.filename) -> \(destination.path)",
            logger: CameraTetheringLogger.transfer
        )
        onPTPAssetReady?(asset, destination)
    }

    private func notifyBaselineIfNeeded() {
        guard !baselineNotified else { return }
        baselineNotified = true
        isCatalogReady = true

        let fileCount = allFiles().count
        CameraTetheringLogger.log("Canon 目录就绪，当前 \(fileCount) 个文件，建立基线")
        onBaselineCatalogReady?()
        startPolling()
    }

    private func startPolling() {
        stopPolling()
        CameraTetheringLogger.log("Canon: 启动轮询检测新照片 (每 1.5s)")

        pollTimer = Timer.scheduledTimer(withTimeInterval: 1.5, repeats: true) { [weak self] _ in
            guard let self, self.isCatalogReady else { return }
            self.onPollTick?()
        }
    }

    private func stopPolling() {
        pollTimer?.invalidate()
        pollTimer = nil
    }
}

extension CanonImageCaptureDeviceManager: ICDeviceBrowserDelegate {
    @objc(deviceBrowser:didAddDevice:moreComing:)
    func deviceBrowser(_ browser: ICDeviceBrowser, didAdd device: ICDevice, moreComing: Bool) {
        CameraTetheringLogger.log("Canon 发现设备: \(device.name ?? "?")")

        guard let camera = device as? ICCameraDevice else { return }
        guard cameraDevice == nil else { return }

        requestAuthorizations { [weak self] contentsGranted, controlGranted in
            guard let self else { return }
            guard contentsGranted else {
                let message = "未获得相机内容访问权限，请在系统设置中允许"
                self.onError?(message)
                self.updateState(.error("权限被拒绝"))
                return
            }
            if !controlGranted {
                CameraTetheringLogger.log("Canon: 控制授权未通过，仍尝试连接")
            }
            self.openSession(for: camera)
        }
    }

    @objc(deviceBrowser:didRemoveDevice:moreGoing:)
    func deviceBrowser(_ browser: ICDeviceBrowser, didRemove device: ICDevice, moreGoing: Bool) {
        guard !isDisconnecting else { return }
        if device.uuidString == cameraDevice?.uuidString {
            Task { await disconnect() }
            updateState(.disconnected)
        }
    }
}

extension CanonImageCaptureDeviceManager: ICDeviceDelegate {
    func didRemove(_ device: ICDevice) {
        guard !isDisconnecting else { return }
        if device.uuidString == cameraDevice?.uuidString {
            Task { await disconnect() }
            updateState(.disconnected)
        }
    }

    func device(_ device: ICDevice, didOpenSessionWithError error: Error?) {
        if let error {
            CameraTetheringLogger.log("Canon delegate didOpenSession 失败: \(error.localizedDescription)")
        }
    }

    func device(_ device: ICDevice, didCloseSessionWithError error: Error?) {
        if device.uuidString == closingDeviceUUID {
            finishSessionClose()
        }
        guard !isDisconnecting else { return }
        if device.uuidString == cameraDevice?.uuidString {
            cameraDevice = nil
            isCatalogReady = false
            baselineNotified = false
            isRemoteSessionReady = false
            stopPolling()
            stopEventPolling()
            updateState(.disconnected)
        }
    }
}

extension CanonImageCaptureDeviceManager: ICCameraDeviceDelegate {
    @objc(cameraDevice:didAddItems:)
    func cameraDevice(_ camera: ICCameraDevice, didAdd items: [ICCameraItem]) {
        let names = items.compactMap { ($0 as? ICCameraFile)?.name ?? $0.name }
        CameraTetheringLogger.log("Canon didAddItems: \(names.joined(separator: ", "))")
        onNewItems?(items)
    }

    @objc(cameraDevice:didRemoveItems:)
    func cameraDevice(_ camera: ICCameraDevice, didRemove items: [ICCameraItem]) {
    }

    @objc(cameraDevice:didRenameItems:)
    func cameraDevice(_ camera: ICCameraDevice, didRenameItems items: [ICCameraItem]) {
    }

    func cameraDevice(_ camera: ICCameraDevice, didReceiveThumbnail thumbnail: CGImage?, for item: ICCameraItem, error: Error?) {
    }

    func cameraDevice(_ camera: ICCameraDevice, didReceiveMetadata metadata: [AnyHashable: Any]?, for item: ICCameraItem, error: Error?) {
    }

    func deviceDidBecomeReady(withCompleteContentCatalog device: ICCameraDevice) {
        CameraTetheringLogger.log("Canon deviceDidBecomeReady, 文件数=\(allFiles().count)")
        notifyBaselineIfNeeded()
    }

    func cameraDeviceDidChangeCapability(_ camera: ICCameraDevice) {
    }

    func cameraDeviceDidRemoveAccessRestriction(_ device: ICDevice) {
    }

    func cameraDeviceDidEnableAccessRestriction(_ device: ICDevice) {
        onError?("设备已锁定，解锁后才能读取媒体文件")
    }

    func cameraDevice(_ camera: ICCameraDevice, didReceivePTPEvent eventData: Data) {
        handlePTPEvent(eventData)
    }
}
