//
//  ConnectionManager.swift
//  otg_swift
//
//  Created by hanqiu on 2026/7/2.
//

@preconcurrency import ImageCaptureCore

/// ConnectionManager 向 ViewModel 上报连接生命周期与内容目录就绪事件。
@MainActor
protocol ConnectionManagerDelegate: AnyObject {
    func connectionManager(_ manager: ConnectionManager, didUpdate state: ConnectionState)
    func connectionManager(_ manager: ConnectionManager, didCreate driver: CameraDriver)
    func connectionManager(_ manager: ConnectionManager, didFail error: Error)
    func connectionManager(_ manager: ConnectionManager, contentCatalogDidBecomeReady driver: CameraDriver)
    func connectionManagerDidSuggestReplug(_ manager: ConnectionManager)
    /// 边拍边传：单张照片已下载并写入目标相册本地缓存。
    func connectionManager(_ manager: ConnectionManager, didSaveLiveShot record: TravelAlbumOTGPhotoRecord)
}

extension ConnectionManagerDelegate {
    func connectionManager(_ manager: ConnectionManager, contentCatalogDidBecomeReady driver: CameraDriver) {}
    func connectionManagerDidSuggestReplug(_ manager: ConnectionManager) {}
    func connectionManager(_ manager: ConnectionManager, didSaveLiveShot record: TravelAlbumOTGPhotoRecord) {}
}

/// 有线相机连接管理抽象，便于页面 ViewModel 测试连接与目录就绪状态。
@MainActor
protocol WiredCameraConnectionManaging: AnyObject {
    var delegate: ConnectionManagerDelegate? { get set }
    var state: ConnectionState { get }
    var currentDriver: CameraDriver? { get }
    var isContentCatalogReady: Bool { get }

    func configureLiveTransfer(albumID: Int?)
    func start()
    func unbindDelegate()
    func suspendLiveTransfer()
    func disconnect()
}

/// 单例：ImageCaptureCore 设备发现、授权、Session、Driver 创建与边拍边传编排。
/// 持有 `ICDeviceBrowser` 与缓存的 `ICCameraDevice`，View 层通过 Delegate 订阅状态。
@MainActor
final class ConnectionManager: NSObject, WiredCameraConnectionManaging {

    static let shared = ConnectionManager()

    weak var delegate: ConnectionManagerDelegate?

    private(set) var state: ConnectionState = .idle {
        didSet {
            if oldValue != state {
                OTGLog.info(.connection, "state changed: \(oldValue) -> \(state)")
            }
            notify { self.delegate?.connectionManager(self, didUpdate: self.state) }
        }
    }

    private(set) var currentDriver: CameraDriver?
    private(set) var isContentCatalogReady = false
    private var browser: ICDeviceBrowser?
    /// First `didAdd` caches the device; kept after session close until USB unplug (`didRemove`).
    private var cachedDevice: ICCameraDevice?
    private var isClosingSession = false
    private var pendingStartAfterClose = false
    private var searchRescanWorkItem: DispatchWorkItem?
    private var searchReplugHintWorkItem: DispatchWorkItem?
    private var sonyRemoteCapture: SonyRemoteCaptureService?
    private var canonRemoteCapture: CanonRemoteCaptureService?
    private var nikonLiveCapture: NikonCatalogLiveCaptureService?
    /// 边拍边传目标相册；由 OTGTransferViewModel 在 start 前设置。
    private var liveTransferAlbumID: Int?

    private let searchRescanDelay: TimeInterval = 1.5
    /// 长时间搜不到设备时提示用户重新插拔
    private let searchReplugHintDelay: TimeInterval = 4.5

    private override init() {
        super.init()
    }

    // MARK: - Public

    /// 设置边拍边传目标相册（索尼 / 佳能）；传 nil 清除。
    func configureLiveTransfer(albumID: Int?) {
        liveTransferAlbumID = albumID
        OTGLog.debug(.connection, "live transfer album=\(albumID.map(String.init) ?? "nil")")
        if albumID == nil {
            stopLiveTransferServices()
            return
        }
        resumeLiveTransferIfPossible()
    }

    /// Entry point when OTG UI appears: reconnect cached device or start discovery.
    func start() {
        OTGLog.info(.connection, "start requested, state=\(state)")

        if case .connected = state {
            return
        }

        if isClosingSession {
            OTGLog.info(.connection, "start deferred, session is closing")
            pendingStartAfterClose = true
            return
        }

        if let cached = cachedDevice {
            reconnect(to: cached)
            return
        }

        startSearching()
    }

    func unbindDelegate() {
        delegate = nil
        cancelSearchTimers()
    }

    /// 页面离开时暂停边拍边传轮询，保留 USB Session 与设备缓存。
    func suspendLiveTransfer() {
        OTGLog.info(.connection, "suspend live transfer")
        liveTransferAlbumID = nil
        stopLiveTransferServices()
    }

    /// 断开 Session 并释放 Driver；缓存设备保留至 USB 拔出。
    func disconnect() {
        guard let device = activeDevice else {
            state = .disconnected
            return
        }

        guard !isClosingSession else {
            OTGLog.info(.connection, "disconnect ignored, session already closing")
            return
        }

        OTGLog.info(.connection, "disconnect requested")
        isClosingSession = true
        isContentCatalogReady = false
        cancelSearchTimers()
        stopLiveTransferServices()
        liveTransferAlbumID = nil
        currentDriver?.disconnect()
        currentDriver = nil
        device.delegate = self
        device.requestCloseSession()
    }

    // MARK: - Private

    private var activeDevice: ICCameraDevice? {
        cachedDevice
    }

    private func notify(_ block: () -> Void) {
        block()
    }

    private func startSearching() {
        OTGLog.info(.connection, "start searching for cameras")
        cancelSearchTimers()
        state = .searching

        if browser == nil {
            launchBrowser()
        }

        scheduleSearchMonitoring()
    }

    private func launchBrowser() {
        let browser = ICDeviceBrowser()
        browser.delegate = self
        // iOS 15.2+：同时浏览 Camera 与 Local USB，提高 Sony/Canon 直连发现率
        if #available(iOS 15.2, *) {
            let mask = ICDeviceTypeMask(
                rawValue: ICDeviceTypeMask.camera.rawValue | ICDeviceLocationTypeMask.local.rawValue
            ) ?? .camera
            browser.browsedDeviceTypeMask = mask
        } else {
            browser.browsedDeviceTypeMask = .camera
        }
        browser.start()
        self.browser = browser
        OTGLog.debug(.connection, "browser launched")
    }

    private func reconnect(to camera: ICCameraDevice) {
        guard !isClosingSession else {
            OTGLog.warning(.connection, "reconnect ignored, session is closing")
            return
        }

        if case .connected = state {
            return
        }

        OTGLog.info(.connection, "reconnecting to cached device: \(camera.name ?? "nil")")
        cancelSearchTimers()
        cachedDevice = camera
        state = .connecting(deviceName: camera.name ?? "Unknown Camera")
        requestAuthorizations { [weak self] in
            self?.openSession(for: camera, context: "cached device")
        }
    }

    /// 先申请 contents 再申请 control；PTP 传图必须获得 control 授权。
    private func requestAuthorizations(then completion: @escaping () -> Void) {
        if browser == nil {
            launchBrowser()
        }

        guard let browser else {
            OTGLog.error(.connection, "authorization skipped, browser unavailable")
            completion()
            return
        }

        browser.requestContentsAuthorization { [weak self] contentsStatus in
            guard let self else { return }

            let contentsGranted = contentsStatus == .authorized
            OTGLog.info(.connection, "contents authorization: \(contentsStatus.rawValue)")

            guard contentsGranted else {
                DispatchQueue.main.async {
                    OTGLog.error(.connection, "camera contents authorization denied")
                    self.state = .failed(message: "未获得相机内容访问权限")
                    self.delegate?.connectionManager(
                        self,
                        didFail: CameraError.connectionFailed("未获得相机内容访问权限")
                    )
                }
                return
            }

            browser.requestControlAuthorization { [weak self] controlStatus in
                guard let self else { return }

                let controlGranted = controlStatus == .authorized
                OTGLog.info(.connection, "control authorization: \(controlStatus.rawValue)")

                DispatchQueue.main.async {
                    guard controlGranted else {
                        OTGLog.error(.connection, "camera control authorization denied, PTP requires control access")
                        self.state = .failed(message: "未获得相机控制权限，无法使用 PTP 传图")
                        self.delegate?.connectionManager(
                            self,
                            didFail: CameraError.connectionFailed(
                                "请在系统弹窗中允许控制外接相机（PTP 传图需要此权限）"
                            )
                        )
                        return
                    }

                    completion()
                }
            }
        }
    }

    private func openSession(for camera: ICCameraDevice, context: String) {
        camera.delegate = self
        camera.requestOpenSession()
        OTGLog.debug(.connection, "requestOpenSession sent (\(context))")
    }

    private func scheduleSearchMonitoring() {
        cancelSearchTimers()

        let rescan = DispatchWorkItem { [weak self] in
            guard let self, self.state == .searching, self.cachedDevice == nil else { return }
            self.rescanBrowser()
        }

        let replugHint = DispatchWorkItem { [weak self] in
            guard let self, self.state == .searching, self.cachedDevice == nil else { return }
            OTGLog.warning(.connection, "search timed out, suggesting replug")
            self.notify { self.delegate?.connectionManagerDidSuggestReplug(self) }
            self.scheduleSearchMonitoring()
        }

        searchRescanWorkItem = rescan
        searchReplugHintWorkItem = replugHint
        DispatchQueue.main.asyncAfter(deadline: .now() + searchRescanDelay, execute: rescan)
        DispatchQueue.main.asyncAfter(deadline: .now() + searchReplugHintDelay, execute: replugHint)
    }

    private func rescanBrowser() {
        OTGLog.info(.connection, "rescanning: restarting browser")
        browser?.stop()
        browser?.delegate = nil
        browser = nil

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
            guard let self, self.state == .searching, self.cachedDevice == nil else { return }
            self.launchBrowser()
            self.scheduleSearchMonitoring()
        }
    }

    private func cancelSearchTimers() {
        searchRescanWorkItem?.cancel()
        searchRescanWorkItem = nil
        searchReplugHintWorkItem?.cancel()
        searchReplugHintWorkItem = nil
    }

    private func clearDeviceCache() {
        stopLiveTransferServices()
        cachedDevice = nil
        currentDriver?.disconnect()
        currentDriver = nil
        isContentCatalogReady = false
        isClosingSession = false
    }
}

extension ConnectionManager: ICDeviceBrowserDelegate {

    func deviceBrowser(_ browser: ICDeviceBrowser, didAdd device: ICDevice, moreComing: Bool) {
        OTGLog.info(.connection, "device added: name=\(device.name ?? "nil"), type=\(device.type.rawValue), moreComing=\(moreComing)")
        guard let camera = device as? ICCameraDevice else {
            OTGLog.warning(.connection, "ignored non-camera device: \(device.name ?? "nil")")
            return
        }
        handleDiscoveredCamera(camera)
    }

    func deviceBrowser(_ browser: ICDeviceBrowser, didRemove device: ICDevice, moreGoing: Bool) {
        OTGLog.info(.connection, "device removed: name=\(device.name ?? "nil"), uuid=\(device.uuidString ?? "nil"), moreGoing=\(moreGoing)")
        guard device.uuidString == cachedDevice?.uuidString else { return }
        cancelSearchTimers()
        clearDeviceCache()
        state = .idle
    }
}

private extension ConnectionManager {

    func handleDiscoveredCamera(_ camera: ICCameraDevice) {
        ICCameraDeviceDebugLogger.logNonEmptyProperties(of: camera, context: "discovered")

        guard !isClosingSession else {
            OTGLog.warning(.connection, "camera ignored while session is closing")
            return
        }

        if let cached = cachedDevice, cached.uuidString == camera.uuidString {
            if case .connected = state { return }
            reconnect(to: cached)
            return
        }

        guard cachedDevice == nil else {
            OTGLog.warning(.connection, "camera ignored, already have cached device: \(cachedDevice?.name ?? "nil")")
            return
        }

        OTGLog.info(.connection, "discovered camera: name=\(camera.name ?? "nil"), uuid=\(camera.uuidString ?? "nil")")
        cancelSearchTimers()
        cachedDevice = camera
        state = .connecting(deviceName: camera.name ?? "Unknown Camera")
        requestAuthorizations { [weak self] in
            self?.openSession(for: camera, context: "discovered")
        }
    }

    func buildDeviceInfo(from camera: ICCameraDevice) -> CameraDeviceInfo {
        let vendorID = Int(camera.usbVendorID)
        let productID = Int(camera.usbProductID)
        let info = CameraDeviceInfo(
            name: camera.name ?? "Unknown Camera",
            manufacturer: camera.productKind,
            model: camera.name,
            serialNumber: camera.uuidString,
            usbVendorID: vendorID != 0 ? vendorID : nil,
            usbProductID: productID != 0 ? productID : nil
        )
        OTGLog.debug(
            .connection,
            "device info: \(info.name), manufacturer=\(info.manufacturer ?? "nil"), usbVendorID=\(info.usbVendorID.map { String(format: "0x%04X", $0) } ?? "nil"), serial=\(info.serialNumber ?? "nil")"
        )
        return info
    }

    /// 检测品牌、创建 Driver 并进入 connected 状态。
    func createDriver(for camera: ICCameraDevice) {
        let info = buildDeviceInfo(from: camera)
        let platform = PlatformDetector.detect(from: info)

        guard platform != .unknown else {
            OTGLog.error(.connection, "unsupported platform for device: \(info.name)")
            state = .failed(message: "不支持的相机品牌")
            delegate?.connectionManager(self, didFail: CameraError.unsupportedPlatform)
            return
        }

        guard let driver = CameraFactory.makeDriver(
            platform: platform,
            device: camera,
            deviceInfo: info
        ) else {
            OTGLog.error(.connection, "failed to create driver for platform: \(platform.rawValue)")
            state = .failed(message: "无法创建相机驱动")
            return
        }

        currentDriver = driver
        state = .connected(platform: platform, deviceName: info.name)
        OTGLog.info(.connection, "driver ready: \(platform.rawValue), device=\(info.name)")
        delegate?.connectionManager(self, didCreate: driver)
    }
    
    /// Session 打开后按品牌配置 PTP：索尼 SDIO + 边拍边传；佳能仅 Probe（远程模式等 catalog 就绪后启动）。
    private func configurePTPAfterSession(_ camera: ICCameraDevice) {
        let platform = PlatformDetector.detect(from: buildDeviceInfo(from: camera))

        Task {
            // 等待 Session 稳定后再发 PTP，避免首包超时
            try? await Task.sleep(nanoseconds: 300_000_000)

            switch platform {
            case .canon:
                await runGetDeviceInfoProbe(on: camera)

            case .sony:
                await startSonyLiveTransferIfNeeded(for: camera)

            default:
                await runGetDeviceInfoProbe(on: camera)
            }
        }
    }

    private func runGetDeviceInfoProbe(on camera: ICCameraDevice) async {
        OTGLog.info(.ptp, "PTP probe starting...")

        var transactionID: UInt32 = 1
        let result = await PTPHelper.sendCommand(
            on: camera,
            code: PTPStandardCommand.getDeviceInfo,
            transactionID: &transactionID,
            label: "GetDeviceInfo(probe)"
        )

        if result.success {
            OTGLog.info(.ptp, "PTP probe passed, deviceInfo=\(result.inData.count) bytes")
        } else {
            OTGLog.error(.ptp, "PTP probe failed")
        }
    }

    /// catalog 就绪后再初始化 Canon 远程模式，避免 SetRemoteMode 打断 SD 卡枚举。
    private func startCanonLiveTransferIfNeeded(for camera: ICCameraDevice) {
        guard canonRemoteCapture == nil else { return }
        guard liveTransferAlbumID != nil else { return }
        guard PlatformDetector.detect(from: buildDeviceInfo(from: camera)) == .canon else { return }

        Task { @MainActor in
            try? await Task.sleep(nanoseconds: 500_000_000)
            guard self.canonRemoteCapture == nil, self.liveTransferAlbumID != nil else { return }

            let transactionID: UInt32 = 1
            let result = await CanonPTPHelper.initializeRemoteSession(
                on: camera,
                startingTransactionID: transactionID
            )
            guard result.success else {
                OTGLog.error(.canon, "Canon remote session setup failed")
                return
            }

            let service = CanonRemoteCaptureService()
            service.onShotSaved = makeLiveShotSavedHandler()
            canonRemoteCapture = service
            service.start(
                camera: camera,
                startingTransactionID: result.nextTransactionID,
                albumID: liveTransferAlbumID
            )
            OTGLog.info(.canon, "Canon remote capture ready")
        }
    }

    /// catalog 就绪后启动尼康边拍边传（didAdd + catalog 轮询，无需 PTP 远程会话）。
    private func startNikonLiveTransferIfNeeded(for camera: ICCameraDevice) {
        guard nikonLiveCapture == nil else { return }
        guard liveTransferAlbumID != nil else { return }
        guard PlatformDetector.detect(from: buildDeviceInfo(from: camera)) == .nikon else { return }

        let service = NikonCatalogLiveCaptureService()
        service.onShotSaved = makeLiveShotSavedHandler()
        nikonLiveCapture = service
        service.start(camera: camera, albumID: liveTransferAlbumID)
        OTGLog.info(.nikon, "Nikon catalog live capture ready")
    }

    private func startSonyLiveTransferIfNeeded(for camera: ICCameraDevice) async {
        guard sonyRemoteCapture == nil else { return }
        guard liveTransferAlbumID != nil else { return }
        guard PlatformDetector.detect(from: buildDeviceInfo(from: camera)) == .sony else { return }

        let transactionID: UInt32 = 1
        let result = await SonyPTPHelper.initializeRemoteSession(
            on: camera,
            startingTransactionID: transactionID
        )
        guard liveTransferAlbumID != nil else { return }
        if result.success {
            let service = SonyRemoteCaptureService()
            service.onShotSaved = makeLiveShotSavedHandler()
            sonyRemoteCapture = service
            service.start(
                camera: camera,
                startingTransactionID: result.nextTransactionID,
                albumID: liveTransferAlbumID
            )
            OTGLog.info(.sony, "Sony remote session ready")
        } else {
            OTGLog.error(.sony, "Sony remote session setup failed")
        }
    }

    private func resumeLiveTransferIfPossible() {
        guard let camera = cachedDevice, case .connected = state else { return }
        switch PlatformDetector.detect(from: buildDeviceInfo(from: camera)) {
        case .sony:
            Task { await startSonyLiveTransferIfNeeded(for: camera) }
        case .canon:
            guard isContentCatalogReady else { return }
            startCanonLiveTransferIfNeeded(for: camera)
        case .nikon:
            guard isContentCatalogReady else { return }
            startNikonLiveTransferIfNeeded(for: camera)
        case .unknown:
            break
        }
    }

    private func stopLiveTransferServices() {
        sonyRemoteCapture?.stop()
        sonyRemoteCapture = nil
        canonRemoteCapture?.stop()
        canonRemoteCapture = nil
        nikonLiveCapture?.stop()
        nikonLiveCapture = nil
    }

    private func makeLiveShotSavedHandler() -> (TravelAlbumOTGPhotoRecord) -> Void {
        { [weak self] record in
            guard let self, let albumID = self.liveTransferAlbumID else { return }
            guard record.albumId == albumID else { return }
            self.notify {
                self.delegate?.connectionManager(
                    self,
                    didSaveLiveShot: record
                )
            }
        }
    }
}

extension ConnectionManager: ICCameraDeviceDelegate {

    func device(_ device: ICDevice, didOpenSessionWithError error: (any Error)?) {
        guard device.uuidString == cachedDevice?.uuidString else { return }

        if let error {
            OTGLog.error(.connection, "open session failed: \(error.localizedDescription)")
            state = .failed(message: error.localizedDescription)
            delegate?.connectionManager(self, didFail: error)
            return
        }

        OTGLog.info(.connection, "session opened: \(device.name ?? "nil")")
        guard let camera = device as? ICCameraDevice else { return }
        cachedDevice = camera
        ICCameraDeviceDebugLogger.logNonEmptyProperties(of: camera, context: "session-opened")
        configurePTPAfterSession(camera)
        createDriver(for: camera)
    }

    func device(_ device: ICDevice, didCloseSessionWithError error: (any Error)?) {
        guard device.uuidString == cachedDevice?.uuidString else { return }

        if let error {
            OTGLog.warning(.connection, "session closed with error: \(error.localizedDescription)")
        } else {
            OTGLog.info(.connection, "session closed: \(device.name ?? "nil")")
        }

        isClosingSession = false
        stopLiveTransferServices()
        currentDriver = nil
        isContentCatalogReady = false
        if let camera = device as? ICCameraDevice {
            cachedDevice = camera
        }
        state = .disconnected

        if pendingStartAfterClose {
            pendingStartAfterClose = false
            OTGLog.info(.connection, "session close finished, resuming start")
            start()
        }
    }

    func cameraDevice(_ camera: ICCameraDevice, didAdd items: [ICCameraItem]) {
        OTGLog.debug(.connection, "camera items added: \(items.count)")
        canonRemoteCapture?.handleCatalogItemsAdded(items)
        nikonLiveCapture?.handleCatalogItemsAdded(items)
    }

    func cameraDevice(_ camera: ICCameraDevice, didRemove items: [ICCameraItem]) {
        OTGLog.debug(.connection, "camera items removed: \(items.count)")
    }

    func cameraDevice(
        _ camera: ICCameraDevice,
        didReceiveThumbnail thumbnail: CGImage?,
        for item: ICCameraItem,
        error: (any Error)?
    ) {
        if let error {
            OTGLog.warning(.connection, "thumbnail failed for \(item.name ?? "nil"): \(error.localizedDescription)")
        }
    }

    func cameraDevice(
        _ camera: ICCameraDevice,
        didReceiveMetadata metadata: [AnyHashable: Any]?,
        for item: ICCameraItem,
        error: (any Error)?
    ) {
        if let error {
            OTGLog.warning(.connection, "metadata failed for \(item.name ?? "nil"): \(error.localizedDescription)")
        }
    }

    func cameraDevice(_ camera: ICCameraDevice, didRenameItems items: [ICCameraItem]) {
        OTGLog.debug(.connection, "camera items renamed: \(items.count)")
    }

    func cameraDeviceDidChangeCapability(_ camera: ICCameraDevice) {
        OTGLog.debug(.connection, "camera capability changed: \(camera.name ?? "nil")")
    }

    func cameraDevice(_ camera: ICCameraDevice, didReceivePTPEvent event: Data) {
        guard cachedDevice?.uuidString == camera.uuidString else { return }
        sonyRemoteCapture?.handlePTPEvent(event)
        canonRemoteCapture?.handlePTPEvent(event)
    }

    /// ImageCaptureCore 内容目录就绪后可安全调用 `listObjects`（MTP 历史导入）。
    func deviceDidBecomeReady(withCompleteContentCatalog camera: ICCameraDevice) {
        guard camera.uuidString == cachedDevice?.uuidString else {
            OTGLog.warning(.connection, "content catalog ready ignored, unknown camera: \(camera.name ?? "nil")")
            return
        }

        OTGLog.info(.connection, "camera ready with content catalog: \(camera.name ?? "nil")")
        ICCameraDeviceDebugLogger.logNonEmptyProperties(of: camera, context: "content-catalog-ready")
        isContentCatalogReady = true

        guard let driver = currentDriver else {
            OTGLog.warning(.connection, "content catalog ready but no driver available")
            return
        }

        notify { self.delegate?.connectionManager(self, contentCatalogDidBecomeReady: driver) }
        startCanonLiveTransferIfNeeded(for: camera)
        startNikonLiveTransferIfNeeded(for: camera)
    }

    func cameraDeviceDidRemoveAccessRestriction(_ device: ICDevice) {
        OTGLog.info(.connection, "access restriction removed: \(device.name ?? "nil")")
    }

    func cameraDeviceDidEnableAccessRestriction(_ device: ICDevice) {
        OTGLog.warning(.connection, "access restriction enabled: \(device.name ?? "nil")")
    }

    func didRemove(_ device: ICDevice) {
        OTGLog.info(.connection, "device removed delegate callback: \(device.name ?? "nil")")
    }
}
