import Foundation
import NitroModules
import NordicDFU

class NativeDfuManager: HybridNativeDfuNitroSpec {
    private var controller: DFUServiceController?
    private var stateCallback: ((_ state: DfuState) -> Void)?
    private var progressCallback: ((_ part: Double, _ totalParts: Double, _ progress: Double, _ currentSpeed: Double, _ avgSpeed: Double) -> Void)?
    private var errorCallback: ((_ error: DfuError, _ message: String) -> Void)?

    func startDfu(
        options: DfuOptions,
        stateCallback: @escaping (_ state: DfuState) -> Void,
        progressCallback: @escaping (_ part: Double, _ totalParts: Double, _ progress: Double, _ currentSpeed: Double, _ avgSpeed: Double) -> Void,
        errorCallback: @escaping (_ error: DfuError, _ message: String) -> Void
    ) throws {
        self.stateCallback = stateCallback
        self.progressCallback = progressCallback
        self.errorCallback = errorCallback

        let fileUrl: URL
        if options.filePath.hasPrefix("file://") {
            guard let url = URL(string: options.filePath) else {
                errorCallback(.fileinvalid, "Invalid file URL: \(options.filePath)")
                return
            }
            fileUrl = url
        } else {
            fileUrl = URL(fileURLWithPath: options.filePath)
        }
        guard let firmware = try? DFUFirmware(urlToZipFile: fileUrl) else {
            errorCallback(.fileinvalid, "Failed to create DFUFirmware from \(options.filePath)")
            return
        }

        guard let uuid = UUID(uuidString: options.deviceId) else {
            errorCallback(.failedtoconnect, "Invalid device UUID: \(options.deviceId)")
            return
        }

        let initiator = DFUServiceInitiator()
        initiator.delegate = self
        initiator.progressDelegate = self
        initiator.logger = self

        if let prn = options.packetReceiptNotificationParameter {
            initiator.packetReceiptNotificationParameter = UInt16(prn)
        }
        if let forceDfu = options.forceDfu {
            initiator.forceDfu = forceDfu
        }
        if let experimental = options.enableUnsafeExperimentalButtonlessDfu {
            initiator.enableUnsafeExperimentalButtonlessServiceInSecureDfu = experimental
        }
        if let forceScan = options.forceScanningForNewAddressInLegacyDfu {
            initiator.forceScanningForNewAddressInLegacyDfu = forceScan
        }
        if let timeout = options.connectionTimeout {
            initiator.connectionTimeout = timeout
        }
        if let delay = options.dataObjectPreparationDelay {
            initiator.dataObjectPreparationDelay = delay
        }
        if let altName = options.alternativeAdvertisingNameEnabled {
            initiator.alternativeAdvertisingNameEnabled = altName
        }
        if let noResume = options.disableResume {
            initiator.disableResume = noResume
        }

        controller = initiator.with(firmware: firmware).start(targetWithIdentifier: uuid)
    }

    func pauseDfu() throws {
        controller?.pause()
    }

    func resumeDfu() throws {
        controller?.resume()
    }

    func abortDfu() throws {
        _ = controller?.abort()
    }
}

// MARK: - DFUServiceDelegate

extension NativeDfuManager: DFUServiceDelegate {
    func dfuStateDidChange(to state: NordicDFU.DFUState) {
        let mapped: DfuState
        switch state {
        case .connecting:
            mapped = .connecting
        case .starting:
            mapped = .starting
        case .enablingDfuMode:
            mapped = .enablingdfumode
        case .uploading:
            mapped = .uploading
        case .validating:
            mapped = .validating
        case .disconnecting:
            mapped = .disconnecting
        case .completed:
            mapped = .completed
        case .aborted:
            mapped = .aborted
        @unknown default:
            mapped = .aborted
        }
        stateCallback?(mapped)
    }

    func dfuError(_ error: NordicDFU.DFUError, didOccurWithMessage message: String) {
        let mapped = mapDfuError(error)
        errorCallback?(mapped, message)
    }

    private func mapDfuError(_ error: NordicDFU.DFUError) -> DfuError {
        switch error {
        // Remote — Legacy DFU
        case .remoteLegacyDFUInvalidState:
            return .remotelegacydfuinvalidstate
        case .remoteLegacyDFUNotSupported:
            return .remotelegacydfunotsupported
        case .remoteLegacyDFUDataExceedsLimit:
            return .remotelegacydfudataexceedslimit
        case .remoteLegacyDFUCrcError:
            return .remotelegacydfucrcerror
        case .remoteLegacyDFUOperationFailed:
            return .remotelegacydfuoperationfailed

        // Remote — Secure DFU
        case .remoteSecureDFUOpCodeNotSupported:
            return .remotesecuredfuopcodenotsupported
        case .remoteSecureDFUInvalidParameter:
            return .remotesecuredfuinvalidparameter
        case .remoteSecureDFUInsufficientResources:
            return .remotesecuredfuinsufficientresources
        case .remoteSecureDFUInvalidObject:
            return .remotesecuredfuinvalidobject
        case .remoteSecureDFUSignatureMismatch:
            return .remotesecuredfusignaturemismatch
        case .remoteSecureDFUUnsupportedType:
            return .remotesecuredfuunsupportedtype
        case .remoteSecureDFUOperationNotPermitted:
            return .remotesecuredfuoperationnotpermitted
        case .remoteSecureDFUOperationFailed:
            return .remotesecuredfuoperationfailed
        case .remoteSecureDFUExtendedError:
            return .remotesecuredfuextendederror

        // Remote — Extended Errors
        case .remoteExtendedErrorWrongCommandFormat:
            return .remoteextendederrorwrongcommandformat
        case .remoteExtendedErrorUnknownCommand:
            return .remoteextendederrorunknowncommand
        case .remoteExtendedErrorInitCommandInvalid:
            return .remoteextendederrorinitcommandinvalid
        case .remoteExtendedErrorFwVersionFailure:
            return .remoteextendederrorfwversionfailure
        case .remoteExtendedErrorHwVersionFailure:
            return .remoteextendederrorhwversionfailure
        case .remoteExtendedErrorSdVersionFailure:
            return .remoteextendederrorsdversionfailure
        case .remoteExtendedErrorSignatureMissing:
            return .remoteextendederrorsignaturemissing
        case .remoteExtendedErrorWrongHashType:
            return .remoteextendederrorwronghashtype
        case .remoteExtendedErrorHashFailed:
            return .remoteextendederrorhashfailed
        case .remoteExtendedErrorWrongSignatureType:
            return .remoteextendederrorwrongsignaturetype
        case .remoteExtendedErrorVerificationFailed:
            return .remoteextendederrorverificationfailed
        case .remoteExtendedErrorInsufficientSpace:
            return .remoteextendederrorinsufficientspace

        // Remote — Buttonless DFU
        case .remoteButtonlessDFUOpCodeNotSupported:
            return .remotebuttonlessdfuopcodenotsupported
        case .remoteButtonlessDFUOperationFailed:
            return .remotebuttonlessdfuoperationfailed
        case .remoteButtonlessDFUInvalidAdvertisementName:
            return .remotebuttonlessdfuinvalidadvertisementname
        case .remoteButtonlessDFUBusy:
            return .remotebuttonlessdfubusy
        case .remoteButtonlessDFUNotBonded:
            return .remotebuttonlessdfunotbonded

        // Remote — Experimental Buttonless DFU
        case .remoteExperimentalButtonlessDFUOpCodeNotSupported:
            return .remoteexperimentalbuttonlessdfuopcodenotsupported
        case .remoteExperimentalButtonlessDFUOperationFailed:
            return .remoteexperimentalbuttonlessdfuoperationfailed

        // File errors
        case .fileNotSpecified:
            return .filenotspecified
        case .fileInvalid:
            return .fileinvalid
        case .extendedInitPacketRequired:
            return .extendedinitpacketrequired
        case .initPacketRequired:
            return .initpacketrequired

        // Connection errors
        case .failedToConnect:
            return .failedtoconnect
        case .deviceDisconnected:
            return .devicedisconnected
        case .bluetoothDisabled:
            return .bluetoothdisabled

        // Service/Protocol errors
        case .serviceDiscoveryFailed:
            return .servicediscoveryfailed
        case .deviceNotSupported:
            return .devicenotsupported
        case .readingVersionFailed:
            return .readingversionfailed
        case .enablingControlPointFailed:
            return .enablingcontrolpointfailed
        case .writingCharacteristicFailed:
            return .writingcharacteristicfailed
        case .receivingNotificationFailed:
            return .receivingnotificationfailed
        case .unsupportedResponse:
            return .unsupportedresponse
        case .bytesLost:
            return .byteslost
        case .crcError:
            return .crcerror
        case .invalidInternalState:
            return .invalidinternalstate

        // Legacy success codes (should not be errors, but handle gracefully)
        case .remoteLegacyDFUSuccess, .remoteSecureDFUSuccess,
             .remoteButtonlessDFUSuccess, .remoteExperimentalButtonlessDFUSuccess:
            return .invalidinternalstate

        @unknown default:
            return .invalidinternalstate
        }
    }
}

// MARK: - DFUProgressDelegate

extension NativeDfuManager: DFUProgressDelegate {
    func dfuProgressDidChange(
        for part: Int,
        outOf totalParts: Int,
        to progress: Int,
        currentSpeedBytesPerSecond: Double,
        avgSpeedBytesPerSecond: Double
    ) {
        progressCallback?(
            Double(part),
            Double(totalParts),
            Double(progress),
            currentSpeedBytesPerSecond,
            avgSpeedBytesPerSecond
        )
    }
}

// MARK: - LoggerDelegate

extension NativeDfuManager: LoggerDelegate {
    func logWith(_ level: LogLevel, message: String) {
        // Logging is handled by the Nordic DFU library internally.
        // Could be forwarded to os_log if needed.
    }
}
