import Foundation
import ImageCaptureCore

enum SonyPTPCommand {
    static let getDeviceInfo: UInt16 = 0x1001
    static let openSession: UInt16 = 0x1002
    static let closeSession: UInt16 = 0x1003
    static let getObjectHandles: UInt16 = 0x1007
    static let getObjectInfo: UInt16 = 0x1008
    static let getObject: UInt16 = 0x1009
    static let objectAdded: UInt16 = 0x4002
    static let sdieObjectAdded: UInt16 = 0xC201
    static let sdieObjectRemoved: UInt16 = 0xC202
    static let sdieDevicePropChanged: UInt16 = 0xC203
    static let sdieCapturedEvent: UInt16 = 0xC206
    static let sdioConnect: UInt16 = 0x9201
    static let sdioGetExtDeviceInfo: UInt16 = 0x9202
    static let sdioSetExtDevicePropValue: UInt16 = 0x9205
    static let sdioGetAllExtDevicePropInfo: UInt16 = 0x9209
    static let positionKeySetting: UInt16 = 0xD25A
    static let stillImageSaveDestination: UInt16 = 0xD222
    static let stillImageTransSize: UInt16 = 0xD268
    static let shootingFileInfo: UInt16 = 0xD215
    static let shotObjectHandle: UInt32 = 0xFFFF_C001
    static let ptpResponseOk: UInt16 = PTPResponseCode.ok.rawValue
    static let saveDestinationHostPC: UInt16 = 1
    static let saveDestinationHostAndCamera: UInt16 = 2
    static let shotBufferReadyThreshold: Int64 = 0x8000
}

private enum PTPDataType: UInt16 {
    case int8 = 0x0001
    case uint8 = 0x0002
    case int16 = 0x0003
    case uint16 = 0x0004
    case int32 = 0x0005
    case uint32 = 0x0006
    case int64 = 0x0007
    case uint64 = 0x0008
    case string = 0xFFFF
}

enum SonyPTPHelper {
    static func buildCommand(
        code: UInt16,
        transactionID: UInt32,
        parameters: [UInt32] = []
    ) -> Data {
        PTPHelper.buildCommand(code: code, transactionID: transactionID, parameters: parameters)
    }

    static func parseContainer(_ data: Data) throws -> PTPContainer {
        try PTPHelper.parseContainer(data)
    }

    static func parseEventCode(from eventData: Data) -> UInt16? {
        PTPHelper.parseEventCode(from: eventData)
    }

    static func parseObjectCompressedSize(_ data: Data) -> UInt32? {
        PTPHelper.parseObjectCompressedSize(data)
    }

    static func parseObjectInfoFilename(_ data: Data) -> String? {
        PTPHelper.parseObjectInfoFilename(data)
    }

    static func parsePTPString(_ data: Data, offset start: Int) -> (string: String, nextOffset: Int)? {
        PTPHelper.parsePTPString(data, offset: start)
    }

    static func readUInt16LE(_ data: Data, offset: Int) -> UInt16 {
        PTPHelper.readUInt16LE(data, offset: offset)
    }

    static func readUInt32LE(_ data: Data, offset: Int) -> UInt32 {
        PTPHelper.readUInt32LE(data, offset: offset)
    }

    /// ImageCaptureCore 已建立 PTP 会话，此处执行 Sony SDIO 握手并进入 PC Remote 模式。
    static func initializeRemoteSession(on camera: ICCameraDevice, startingTransactionID: UInt32) async -> UInt32 {
        var transactionID = startingTransactionID

        CameraTetheringLogger.log("开始 Sony SDIO 握手", logger: CameraTetheringLogger.ptp)

        let step1 = await sendCommand(
            on: camera,
            code: SonyPTPCommand.sdioConnect,
            transactionID: &transactionID,
            parameters: [1, 0, 0],
            outData: nil,
            label: "SDIO Connect(1)"
        )
        guard step1.success else { return transactionID }

        let step2 = await sendCommand(
            on: camera,
            code: SonyPTPCommand.sdioConnect,
            transactionID: &transactionID,
            parameters: [2, 0, 0],
            outData: nil,
            label: "SDIO Connect(2)"
        )
        guard step2.success else { return transactionID }

        let step3 = await sendCommand(
            on: camera,
            code: SonyPTPCommand.sdioGetExtDeviceInfo,
            transactionID: &transactionID,
            parameters: [0x012C, 1],
            outData: nil,
            label: "SDIO GetExtDeviceInfo"
        )
        guard step3.success else { return transactionID }

        let step4 = await sendCommand(
            on: camera,
            code: SonyPTPCommand.sdioConnect,
            transactionID: &transactionID,
            parameters: [3, 0, 0],
            outData: nil,
            label: "SDIO Connect(3)"
        )
        guard step4.success else { return transactionID }

        _ = await sendCommand(
            on: camera,
            code: SonyPTPCommand.sdioGetAllExtDevicePropInfo,
            transactionID: &transactionID,
            parameters: [1],
            outData: nil,
            label: "SDIO GetAllExtDevicePropInfo"
        )

        await configureRemoteCapture(on: camera, transactionID: &transactionID)
        CameraTetheringLogger.log("Sony SDIO 握手完成", logger: CameraTetheringLogger.ptp)
        return transactionID
    }

    /// 关闭 Sony PC Remote 并释放 PTP 会话。
    static func teardownRemoteSession(on camera: ICCameraDevice, transactionID: inout UInt32) async {
        _ = await setDeviceProperty(
            on: camera,
            propertyCode: SonyPTPCommand.positionKeySetting,
            value: 0,
            transactionID: &transactionID,
            label: "Position_Key_Setting=Local"
        )
        await closeRemoteSession(on: camera, transactionID: &transactionID)
    }

    /// 关闭 PTP 远程会话，释放相机 PC Remote 占用。
    static func closeRemoteSession(on camera: ICCameraDevice, transactionID: inout UInt32) async {
        _ = await sendCommand(
            on: camera,
            code: SonyPTPCommand.closeSession,
            transactionID: &transactionID,
            parameters: [],
            outData: nil,
            label: "CloseSession"
        )
    }

    static func probeShotObject(
        on camera: ICCameraDevice,
        handle: UInt32 = SonyPTPCommand.shotObjectHandle,
        transactionID: inout UInt32
    ) async -> (filename: String, size: UInt32)? {
        let infoResult = await sendCommand(
            on: camera,
            code: SonyPTPCommand.getObjectInfo,
            transactionID: &transactionID,
            parameters: [handle],
            outData: nil,
            label: "ProbeObjectInfo(0x\(String(format: "%08X", handle)))"
        )
        guard infoResult.success else { return nil }

        guard let size = parseObjectCompressedSize(infoResult.inData), size > 0 else {
            return nil
        }

        let filename = parseObjectInfoFilename(infoResult.inData)
            ?? CameraDownloadStorage.sanitizeFilename("DSC_\(Int(Date().timeIntervalSince1970)).JPG")
        return (filename, size)
    }

    static func downloadObject(
        handle: UInt32,
        on camera: ICCameraDevice,
        transactionID: inout UInt32,
        skipObjectInfo: Bool = false
    ) async -> (filename: String, data: Data)? {
        var filename = CameraDownloadStorage.sanitizeFilename("DSC_\(Int(Date().timeIntervalSince1970)).JPG")

        if !skipObjectInfo {
            let infoResult = await sendCommand(
                on: camera,
                code: SonyPTPCommand.getObjectInfo,
                transactionID: &transactionID,
                parameters: [handle],
                outData: nil,
                label: "GetObjectInfo(0x\(String(format: "%08X", handle)))"
            )
            if infoResult.success, let parsedName = parseObjectInfoFilename(infoResult.inData) {
                filename = parsedName
            }
        }

        let objectResult = await sendCommand(
            on: camera,
            code: SonyPTPCommand.getObject,
            transactionID: &transactionID,
            parameters: [handle],
            outData: nil,
            label: "GetObject(0x\(String(format: "%08X", handle)))"
        )
        guard objectResult.success, PTPHelper.isLikelyImageData(objectResult.inData) else { return nil }

        return (filename, objectResult.inData)
    }

    static func isLikelyImageData(_ data: Data) -> Bool {
        PTPHelper.isLikelyImageData(data)
    }

    static func isResponseOK(_ response: Data) -> Bool {
        PTPHelper.isResponseOK(response)
    }

    static func fetchExtDeviceProperties(
        on camera: ICCameraDevice,
        transactionID: inout UInt32,
        onlyDiff: Bool = true
    ) async -> [UInt16: Int64] {
        let result = await sendCommand(
            on: camera,
            code: SonyPTPCommand.sdioGetAllExtDevicePropInfo,
            transactionID: &transactionID,
            parameters: [onlyDiff ? 1 : 0],
            outData: nil,
            label: "GetAllExtDevicePropInfo(\(onlyDiff ? "diff" : "all"))"
        )
        guard result.success else { return [:] }
        return parseExtDeviceProperties(result.inData)
    }

    static func parseExtDeviceProperties(_ data: Data) -> [UInt16: Int64] {
        guard data.count >= 8 else { return [:] }

        var properties: [UInt16: Int64] = [:]
        var offset = 8

        while offset + 6 <= data.count {
            let propertyCode = readUInt16LE(data, offset: offset)
            offset += 2
            let dataType = readUInt16LE(data, offset: offset)
            offset += 2
            offset += 2 // getset, isenabled

            guard skipVariableValue(dataType: dataType, data: data, offset: &offset) else { break }
            guard let currentValue = readVariableValue(dataType: dataType, data: data, offset: &offset) else { break }
            properties[propertyCode] = currentValue

            guard offset < data.count else { break }
            let formFlag = data[offset]
            offset += 1
            guard skipFormPayload(formFlag: formFlag, dataType: dataType, data: data, offset: &offset) else { break }
        }

        return properties
    }

    static func isShotBufferReady(_ shootingFileInfo: Int64?) -> Bool {
        guard let shootingFileInfo else { return false }
        return shootingFileInfo > SonyPTPCommand.shotBufferReadyThreshold
    }

    static func shotObjectSignature(filename: String, size: UInt32) -> String {
        "\(filename)_\(size)"
    }

    private static func readVariableValue(dataType: UInt16, data: Data, offset: inout Int) -> Int64? {
        guard let type = PTPDataType(rawValue: dataType) else { return nil }

        switch type {
        case .int8:
            guard offset < data.count else { return nil }
            let value = Int64(Int8(bitPattern: data[offset]))
            offset += 1
            return value
        case .uint8:
            guard offset < data.count else { return nil }
            let value = Int64(data[offset])
            offset += 1
            return value
        case .int16:
            guard offset + 1 < data.count else { return nil }
            let value = Int64(Int16(bitPattern: readUInt16LE(data, offset: offset)))
            offset += 2
            return value
        case .uint16:
            guard offset + 1 < data.count else { return nil }
            let value = Int64(readUInt16LE(data, offset: offset))
            offset += 2
            return value
        case .int32:
            guard offset + 3 < data.count else { return nil }
            let value = Int64(Int32(bitPattern: readUInt32LE(data, offset: offset)))
            offset += 4
            return value
        case .uint32:
            guard offset + 3 < data.count else { return nil }
            let value = Int64(readUInt32LE(data, offset: offset))
            offset += 4
            return value
        case .int64, .uint64:
            guard offset + 7 < data.count else { return nil }
            offset += 8
            return 0
        case .string:
            guard offset < data.count else { return nil }
            let length = Int(data[offset])
            offset += 1 + length * 2
            return 0
        }
    }

    private static func skipVariableValue(dataType: UInt16, data: Data, offset: inout Int) -> Bool {
        readVariableValue(dataType: dataType, data: data, offset: &offset) != nil
    }

    private static func skipFormPayload(formFlag: UInt8, dataType: UInt16, data: Data, offset: inout Int) -> Bool {
        switch formFlag {
        case 0:
            return true
        case 1:
            for _ in 0..<3 {
                guard skipVariableValue(dataType: dataType, data: data, offset: &offset) else { return false }
            }
            return true
        case 2:
            guard offset + 1 < data.count else { return false }
            var enumCount = Int(readUInt16LE(data, offset: offset))
            offset += 2
            for _ in 0..<enumCount {
                guard skipVariableValue(dataType: dataType, data: data, offset: &offset) else { return false }
            }
            guard offset + 1 < data.count else { return false }
            enumCount = Int(readUInt16LE(data, offset: offset))
            offset += 2
            for _ in 0..<enumCount {
                guard skipVariableValue(dataType: dataType, data: data, offset: &offset) else { return false }
            }
            return true
        default:
            return true
        }
    }

    private static func configureRemoteCapture(on camera: ICCameraDevice, transactionID: inout UInt32) async {
        _ = await setDeviceProperty(
            on: camera,
            propertyCode: SonyPTPCommand.positionKeySetting,
            value: 1,
            transactionID: &transactionID,
            label: "Position_Key_Setting=PC Remote"
        )

        _ = await setDeviceProperty(
            on: camera,
            propertyCode: SonyPTPCommand.stillImageSaveDestination,
            value: SonyPTPCommand.saveDestinationHostPC,
            transactionID: &transactionID,
            label: "Still_Image_Save_Destination=Host PC"
        )

        _ = await setDevicePropertyUInt32(
            on: camera,
            propertyCode: SonyPTPCommand.stillImageTransSize,
            value: 0xFFFF_FFFF,
            transactionID: &transactionID,
            label: "Still_Image_Trans_Size=Full"
        )
    }

    private static func setDevicePropertyUInt32(
        on camera: ICCameraDevice,
        propertyCode: UInt16,
        value: UInt32,
        transactionID: inout UInt32,
        label: String
    ) async -> Bool {
        var payload = Data()
        PTPHelper.appendUInt32LE(value, to: &payload)

        return await sendCommand(
            on: camera,
            code: SonyPTPCommand.sdioSetExtDevicePropValue,
            transactionID: &transactionID,
            parameters: [UInt32(propertyCode), 1],
            outData: payload,
            label: label
        ).success
    }

    private static func setDeviceProperty(
        on camera: ICCameraDevice,
        propertyCode: UInt16,
        value: UInt16,
        transactionID: inout UInt32,
        label: String
    ) async -> Bool {
        var payload = Data()
        PTPHelper.appendUInt16LE(value, to: &payload)

        return await sendCommand(
            on: camera,
            code: SonyPTPCommand.sdioSetExtDevicePropValue,
            transactionID: &transactionID,
            parameters: [UInt32(propertyCode), 1],
            outData: payload,
            label: label
        ).success
    }

    private static func sendCommand(
        on camera: ICCameraDevice,
        code: UInt16,
        transactionID: inout UInt32,
        parameters: [UInt32],
        outData: Data?,
        label: String
    ) async -> PTPCommandResult {
        await PTPHelper.sendCommand(
            on: camera,
            code: code,
            transactionID: &transactionID,
            parameters: parameters,
            outData: outData,
            label: label
        )
    }
}
