import Foundation

/// Canon EOS GetEvent 返回的事件 blob 解析器。
enum CanonEosEventParser {
    private static let recordHeaderSize = 8
    private static let objectAddedExMinPayload = 33
    private static let objectAddedEx2MinPayload = 41

    /// 解析后的 Canon 事件。
    enum ParsedEvent {
        case objectAdded(ObjectAdded)
        case requestObjectTransfer(handle: UInt32)
        case cameraStatus(status: Int32)
    }

    /// ObjectAddedEx / ObjectAddedEx2 事件载荷。
    struct ObjectAdded {
        let objectHandle: UInt32
        let storageId: UInt32
        let formatCode: UInt16
        let parentHandle: UInt32
        let size: Int64
        let filename: String
    }

    /// 解析 GetEvent 返回的二进制 blob。
    static func parse(_ blob: Data) -> [ParsedEvent] {
        guard !blob.isEmpty else { return [] }

        var events: [ParsedEvent] = []
        var offset = 0

        while offset + recordHeaderSize <= blob.count {
            let recordStart = offset
            let recordSizeRaw = Int64(PTPHelper.readUInt32LE(blob, offset: offset))
            offset += 4
            if recordSizeRaw == 0 { break }
            if recordSizeRaw < Int64(recordHeaderSize) { break }

            let recordSize = Int(recordSizeRaw)
            let type = Int(PTPHelper.readUInt32LE(blob, offset: offset) & 0xFFFF)
            offset += 4
            let payloadSize = recordSize - recordHeaderSize

            if type == 0, payloadSize == 0 { break }
            if offset + payloadSize > blob.count { break }

            let payloadEnd = offset + payloadSize

            switch UInt16(type) {
            case CanonPTPCommand.eosObjectAddedEx:
                if let added = parseObjectAdded(
                    blob,
                    offset: offset,
                    payloadSize: payloadSize,
                    filenameOffset: 32,
                    minPayload: objectAddedExMinPayload
                ) {
                    events.append(.objectAdded(added))
                }
            case CanonPTPCommand.eosObjectAddedEx2:
                if let added = parseObjectAdded(
                    blob,
                    offset: offset,
                    payloadSize: payloadSize,
                    filenameOffset: 40,
                    minPayload: objectAddedEx2MinPayload
                ) {
                    events.append(.objectAdded(added))
                }
            case CanonPTPCommand.eosRequestObjectTransfer:
                let handle: UInt32 = payloadSize >= 4 ? PTPHelper.readUInt32LE(blob, offset: offset) : 0
                events.append(.requestObjectTransfer(handle: handle))
            case CanonPTPCommand.eosCameraStatusChanged:
                let status: Int32 = payloadSize >= 4
                    ? Int32(bitPattern: PTPHelper.readUInt32LE(blob, offset: offset))
                    : 0
                events.append(.cameraStatus(status: status))
            case CanonPTPCommand.eosObjectInfoChangedEx,
                 CanonPTPCommand.eosStorageInfoChanged:
                break
            default:
                break
            }

            offset = payloadEnd
            if offset == recordStart { break }
        }

        return events
    }

    /// 将 ObjectAdded 事件转为 CameraAsset。
    static func makeAsset(from event: ObjectAdded) -> CameraAsset {
        let filename = event.filename.isEmpty
            ? "IMG_\(event.objectHandle).JPG"
            : CameraDownloadStorage.sanitizeFilename(event.filename)
        let isMovie = isVideoFormat(event.formatCode) || isVideoExtension(filename)
        return CameraAsset(
            id: CanonPTPHelper.assetID(handle: event.objectHandle, filename: filename, size: event.size),
            filename: filename,
            fileSize: event.size,
            creationDate: Date(),
            isMovie: isMovie
        )
    }

    private static func parseObjectAdded(
        _ blob: Data,
        offset payloadStart: Int,
        payloadSize: Int,
        filenameOffset: Int,
        minPayload: Int
    ) -> ObjectAdded? {
        guard payloadSize >= minPayload else { return nil }

        var offset = payloadStart
        let objectHandle = PTPHelper.readUInt32LE(blob, offset: offset)
        offset += 4
        let storageId = PTPHelper.readUInt32LE(blob, offset: offset)
        offset += 4
        let ofc = UInt16(PTPHelper.readUInt32LE(blob, offset: offset) & 0xFFFF)
        offset += 4

        let size: Int64
        let parentHandle: UInt32

        if filenameOffset == 32 {
            offset += 4 // flags
            parentHandle = PTPHelper.readUInt32LE(blob, offset: offset)
            offset += 4
            offset += 4 // reserved
            size = Int64(PTPHelper.readUInt32LE(blob, offset: offset))
            offset += 4
            offset += 4 // reserved
        } else {
            offset += 4 // reserved
            parentHandle = PTPHelper.readUInt32LE(blob, offset: offset)
            offset += 4
            let sizeLow = Int64(PTPHelper.readUInt32LE(blob, offset: offset))
            offset += 4
            let sizeHigh = Int64(PTPHelper.readUInt32LE(blob, offset: offset))
            offset += 4
            size = sizeLow | (sizeHigh << 32)
            offset += 4 // reserved
            offset += 4 // relatedHandle
        }

        let expected = payloadStart + filenameOffset
        if offset != expected { offset = expected }

        let filenameBytesAvailable = payloadSize - filenameOffset
        guard filenameBytesAvailable > 0, offset + filenameBytesAvailable <= blob.count else { return nil }

        let filenameData = blob.subdata(in: offset..<(offset + filenameBytesAvailable))
        let filename = extractCString(filenameData)

        return ObjectAdded(
            objectHandle: objectHandle,
            storageId: storageId,
            formatCode: ofc,
            parentHandle: parentHandle,
            size: size,
            filename: filename
        )
    }

    private static func extractCString(_ bytes: Data) -> String {
        if let nulIndex = bytes.firstIndex(of: 0) {
            return String(bytes: bytes.prefix(upTo: nulIndex), encoding: .ascii)?.trimmingCharacters(in: .whitespaces) ?? ""
        }
        return String(bytes: bytes, encoding: .ascii)?.trimmingCharacters(in: .whitespaces) ?? ""
    }

    private static func isVideoFormat(_ ofc: UInt16) -> Bool {
        switch ofc {
        case 0x300D, 0x3000, 0xB982, 0xB983, 0x300F:
            return true
        default:
            return false
        }
    }

    private static func isVideoExtension(_ filename: String) -> Bool {
        let lower = filename.lowercased()
        return [".mov", ".mp4", ".m4v", ".avi", ".mpg", ".mpeg"].contains { lower.hasSuffix($0) }
    }
}
