//
//  SonyRemoteCaptureService.swift
//  otg_swift
//

import Foundation
@preconcurrency import ImageCaptureCore

/// Sony PC Remote 边拍边传：检测（Step 5）+ GetObject 下载入库（Step 6）。
final class SonyRemoteCaptureService {

    private weak var camera: ICCameraDevice?
    private var transactionID: UInt32 = 1
    private(set) var isReady = false

    private var albumID: Int?
    private let photoRepository: PhotoRepositoryProtocol
    /// 下载成功时回调本地照片记录，供 ConnectionManager 转发给 ViewModel。
    var onShotSaved: ((TravelAlbumOTGPhotoRecord) -> Void)?

    private var pollTimer: Timer?
    private var propertyCheckTask: Task<Void, Never>?
    private var isEvaluatingShotBuffer = false
    private var propertyCheckCoalesceScheduled = false
    private var isDownloading = false

    private var lastDownloadedShotSignature: String?
    private var detectedShotCount = 0
    private var savedShotCount = 0

    private let idlePollInterval: TimeInterval = 1.0
    /// 检测到新片后加快 D215 轮询，提高连拍响应
    private let activePollInterval: TimeInterval = 0.25
    /// C203 等事件后略延迟再读属性，等待相机写入缓冲区
    private let propertyCheckDelay: TimeInterval = 0.12

    init(photoRepository: PhotoRepositoryProtocol = TravelAlbumOTGPhotoRepository()) {
        self.photoRepository = photoRepository
    }

    // MARK: - Lifecycle

    /// 在 SDIO 握手成功后启动；`albumID` 为边拍边传目标相册，nil 时只检测不保存。
    func start(
        camera: ICCameraDevice,
        startingTransactionID: UInt32,
        albumID: Int?
    ) {
        stop()

        self.camera = camera
        self.transactionID = startingTransactionID
        self.albumID = albumID
        self.isReady = true
        self.lastDownloadedShotSignature = nil
        self.detectedShotCount = 0
        self.savedShotCount = 0

        OTGLog.info(.sony, "Remote capture service started, transactionID=\(startingTransactionID), album=\(albumID.map(String.init) ?? "nil")")
        startD215Polling()
    }

    func stop() {
        isReady = false
        stopD215Polling()
        propertyCheckTask?.cancel()
        propertyCheckTask = nil
        isEvaluatingShotBuffer = false
        propertyCheckCoalesceScheduled = false
        isDownloading = false
        albumID = nil
        camera = nil
    }

    // MARK: - PTP Events

    /// 处理 Sony SDIE 事件：C201/C206 强信号，C203 需配合 D215/Probe。
    func handlePTPEvent(_ eventData: Data) {
        guard isReady else { return }

        guard let code = PTPHelper.parseEventCode(from: eventData) else { return }

        switch code {
        case SonyPTPCommand.sdieObjectAdded:
            let handle = (try? PTPHelper.parseContainer(eventData))?.params.first
                ?? SonyPTPCommand.shotObjectHandle
            OTGLog.info(.sony, "Shot signal: ObjectAdded handle=0x\(String(format: "%08X", handle))")
            schedulePropertyBasedShotCheck(reason: "ObjectAdded")

        case SonyPTPCommand.sdieCapturedEvent:
            OTGLog.info(.sony, "Shot signal: CapturedEvent")
            schedulePropertyBasedShotCheck(reason: "CapturedEvent")

        case SonyPTPCommand.sdieDevicePropChanged:
            OTGLog.debug(.sony, "PTP event: DevicePropChanged (0xC203)")
            schedulePropertyBasedShotCheck(reason: "DevicePropChanged")

        case SonyPTPCommand.sdieObjectRemoved:
            OTGLog.debug(.sony, "PTP event: ObjectRemoved (0xC202)")

        default:
            OTGLog.debug(.sony, "PTP event: \(SonyPTPHelper.describeEvent(eventData))")
        }
    }

    // MARK: - D215 Polling

    private func startD215Polling() {
        stopD215Polling()
        OTGLog.info(.sony, "D215 polling started (idle=\(idlePollInterval)s)")
        schedulePollTimer(interval: idlePollInterval)
    }

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

    private func schedulePollTimer(interval: TimeInterval) {
        stopD215Polling()
        pollTimer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in
            self?.schedulePropertyBasedShotCheck(reason: "D215Poll", delay: 0)
        }
    }

    private func refreshPollInterval() {
        guard isReady else { return }
        let interval = savedShotCount > 0 ? activePollInterval : idlePollInterval
        if pollTimer?.timeInterval != interval {
            schedulePollTimer(interval: interval)
        }
    }

    // MARK: - Property check + Probe + Download

    private func schedulePropertyBasedShotCheck(reason: String, delay: TimeInterval? = nil) {
        // 正在评估时合并后续请求，避免并发 PTP 风暴
        if isEvaluatingShotBuffer {
            propertyCheckCoalesceScheduled = true
            return
        }

        propertyCheckTask?.cancel()
        let wait = delay ?? propertyCheckDelay
        propertyCheckTask = Task { [weak self] in
            if wait > 0 {
                try? await Task.sleep(nanoseconds: UInt64(wait * 1_000_000_000))
            }
            guard !Task.isCancelled else { return }
            await self?.runPropertyBasedShotCheck(reason: reason)
        }
    }

    private func runPropertyBasedShotCheck(reason: String) async {
        if isEvaluatingShotBuffer {
            propertyCheckCoalesceScheduled = true
            return
        }

        isEvaluatingShotBuffer = true
        defer {
            isEvaluatingShotBuffer = false
            if propertyCheckCoalesceScheduled {
                propertyCheckCoalesceScheduled = false
                schedulePropertyBasedShotCheck(reason: "Coalesced", delay: 0.08)
            }
        }

        await evaluateShotBuffer(reason: reason)
    }

    /// 读 D215 → 必要时 Probe → GetObject 下载入库。
    private func evaluateShotBuffer(reason: String) async {
        guard isReady, let camera else { return }

        var localTransactionID = transactionID
        var properties = await SonyPTPHelper.fetchExtDeviceProperties(
            on: camera,
            transactionID: &localTransactionID,
            onlyDiff: true
        )
        transactionID = localTransactionID

        var shootingFileInfo = properties[SonyPTPCommand.shootingFileInfo]

        if shootingFileInfo == nil, shouldFallbackFullPropertyFetch(for: reason) {
            properties = await SonyPTPHelper.fetchExtDeviceProperties(
                on: camera,
                transactionID: &localTransactionID,
                onlyDiff: false
            )
            transactionID = localTransactionID
            shootingFileInfo = properties[SonyPTPCommand.shootingFileInfo]
            OTGLog.debug(.sony, "\(reason): diff had no D215, fetched full property set")
        }

        if let shootingFileInfo {
            OTGLog.debug(
                .sony,
                "\(reason): D215=0x\(String(format: "%04X", shootingFileInfo))"
            )
        }

        if SonyPTPHelper.isShotBufferReady(shootingFileInfo) {
            _ = await attemptFetchPendingShot(reason: "\(reason)-D215")
            refreshPollInterval()
            return
        }

        // ZV-E10 等机型快门可能只发 C203 且 D215 未及时更新，仍尝试 Probe
        if shouldProbeWithoutD215(for: reason) {
            _ = await attemptFetchPendingShot(reason: "\(reason)-Probe")
        }

        refreshPollInterval()
    }

    /// Probe → 去重 → GetObject → 写入相册目录与 Core Data。
    @discardableResult
    private func attemptFetchPendingShot(reason: String) async -> Bool {
        guard isReady, let camera else { return false }

        var localTransactionID = transactionID
        guard let probe = await SonyPTPHelper.probeShotObject(
            on: camera,
            transactionID: &localTransactionID
        ) else {
            return false
        }
        transactionID = localTransactionID

        let signature = SonyPTPHelper.shotObjectSignature(
            filename: probe.filename,
            size: probe.size
        )

        if signature == lastDownloadedShotSignature {
            OTGLog.debug(.sony, "\(reason): already downloaded signature=\(signature)")
            return false
        }

        detectedShotCount += 1
        OTGLog.info(
            .sony,
            "New shot detected (#\(detectedShotCount)): \(probe.filename), size=\(probe.size), reason=\(reason)"
        )

        guard await downloadAndSave(probe: probe, reason: reason) else {
            return false
        }

        lastDownloadedShotSignature = signature
        savedShotCount += 1
        return true
    }

    /// 串行下载，失败时最多重试 3 次（连拍缓冲区可能尚未写完）。
    private func downloadAndSave(probe: SonyPTPHelper.PendingShot, reason: String) async -> Bool {
        guard isReady, let camera else { return false }

        guard let albumID else {
            OTGLog.warning(.sony, "\(reason): live transfer album not configured, skip save")
            return false
        }

        if isDownloading {
            OTGLog.debug(.sony, "\(reason): download already in progress, skip")
            return false
        }

        isDownloading = true
        defer { isDownloading = false }

        var localTransactionID = transactionID

        for attempt in 1...3 {
            if attempt > 1 {
                try? await Task.sleep(nanoseconds: UInt64(attempt) * 350_000_000)
            }

            guard let downloaded = await SonyPTPHelper.downloadShotObject(
                on: camera,
                handle: probe.handle,
                transactionID: &localTransactionID,
                knownFilename: probe.filename,
                skipObjectInfo: true
            ) else {
                if attempt == 3 {
                    OTGLog.error(.sony, "\(reason): GetObject failed after \(attempt) attempts")
                }
                continue
            }
            transactionID = localTransactionID

            do {
                let fileURL = try AlbumPhotoStorage.writeImage(
                    downloaded.data,
                    filename: downloaded.filename,
                    albumID: albumID
                )
                let record = try photoRepository.addPhoto(
                    albumID: albumID,
                    localPath: fileURL.path,
                    createdAt: Date()
                )
                OTGLog.info(
                    .sony,
                    "Live shot saved (#\(savedShotCount + 1)): \(downloaded.filename), bytes=\(downloaded.data.count), path=\(fileURL.lastPathComponent)"
                )
                onShotSaved?(record)
                return true
            } catch {
                OTGLog.error(.sony, "\(reason): save failed: \(error.localizedDescription)")
                return false
            }
        }

        return false
    }

    private func shouldFallbackFullPropertyFetch(for reason: String) -> Bool {
        reason.contains("DevicePropChanged")
            || reason.hasPrefix("D215Poll")
            || reason.hasPrefix("Coalesced")
    }

    private func shouldProbeWithoutD215(for reason: String) -> Bool {
        reason.contains("DevicePropChanged")
            || reason.hasPrefix("D215Poll")
            || reason.hasPrefix("Coalesced")
            || reason.contains("ObjectAdded")
            || reason.contains("CapturedEvent")
    }
}
