#import "classic_bluetooth.h"
#import <IOBluetooth/IOBluetooth.h>
#import <Foundation/Foundation.h>
#include <map>
#include <mutex>
#include <chrono>
#include <iomanip>
#include <sstream>

// Objective-C delegate for IOBluetoothDeviceInquiry
@interface BluetoothInquiryDelegate : NSObject <IOBluetoothDeviceInquiryDelegate>
{
    ClassicBluetooth::DeviceFoundCallback deviceFoundCallback;
    ClassicBluetooth::InquiryCompleteCallback inquiryCompleteCallback;
    std::vector<ClassicBluetooth::ClassicDevice>* discoveredDevices;
    std::mutex* devicesMutex;
}

- (instancetype)initWithCallbacks:(ClassicBluetooth::DeviceFoundCallback)foundCb
                       completeCb:(ClassicBluetooth::InquiryCompleteCallback)completeCb
                          devices:(std::vector<ClassicBluetooth::ClassicDevice>*)devices
                            mutex:(std::mutex*)mutex;

@end

@implementation BluetoothInquiryDelegate

- (instancetype)initWithCallbacks:(ClassicBluetooth::DeviceFoundCallback)foundCb
                       completeCb:(ClassicBluetooth::InquiryCompleteCallback)completeCb
                          devices:(std::vector<ClassicBluetooth::ClassicDevice>*)devices
                            mutex:(std::mutex*)mutex {
    self = [super init];
    if (self) {
        deviceFoundCallback = foundCb;
        inquiryCompleteCallback = completeCb;
        discoveredDevices = devices;
        devicesMutex = mutex;
    }
    return self;
}

- (void)deviceInquiryDeviceFound:(IOBluetoothDeviceInquiry*)sender
                          device:(IOBluetoothDevice*)device {

    ClassicBluetooth::ClassicDevice classicDevice;

    // Basic info
    classicDevice.address = [[device addressString] UTF8String];
    classicDevice.name = device.name ? [device.name UTF8String] : "<unnamed>";
    classicDevice.rssi = [device rawRSSI];
    classicDevice.is_connected = [device isConnected];
    classicDevice.is_paired = [device isPaired];

    // Device class
    BluetoothClassOfDevice cod = [device classOfDevice];
    classicDevice.device_class.class_of_device = cod;
    classicDevice.device_class.major_class = ClassicBluetooth::decode_device_class_major(cod);
    classicDevice.device_class.minor_class = ClassicBluetooth::decode_device_class_minor(cod);

    // Timestamp
    auto now = std::chrono::system_clock::now();
    auto now_time_t = std::chrono::system_clock::to_time_t(now);
    std::tm tm_buf;
    gmtime_r(&now_time_t, &tm_buf);
    std::ostringstream oss;
    oss << std::put_time(&tm_buf, "%Y-%m-%d %H:%M:%S");
    classicDevice.last_seen = oss.str();

    // Get SDP services (if available)
    NSArray *services = [device services];
    if (services && [services count] > 0) {
        for (IOBluetoothSDPServiceRecord *record in services) {
            ClassicBluetooth::SDPService sdpService;

            // Get RFCOMM channel (for SPP services)
            BluetoothRFCOMMChannelID channelID;
            if ([record getRFCOMMChannelID:&channelID] == kIOReturnSuccess) {
                sdpService.rfcomm_channel = channelID;
            } else {
                sdpService.rfcomm_channel = 0;
            }

            // Try to get service UUID from service record
            BluetoothSDPServiceRecordHandle handle;
            if ([record getServiceRecordHandle:&handle] == kIOReturnSuccess) {
                char uuidStr[10];
                snprintf(uuidStr, sizeof(uuidStr), "%04X", (uint16_t)handle);
                sdpService.uuid = uuidStr;
            }

            // Get service name if available
            NSString *serviceName = nil;
            IOBluetoothSDPDataElement *nameElement = [record getAttributeDataElement:kBluetoothSDPAttributeIdentifierServiceName];
            if (nameElement) {
                serviceName = [nameElement getStringValue];
            }

            if (serviceName) {
                sdpService.name = [serviceName UTF8String];
            } else if (!sdpService.uuid.empty()) {
                sdpService.name = ClassicBluetooth::decode_service_uuid(sdpService.uuid);
            } else {
                sdpService.name = "Unknown Service";
            }

            classicDevice.services.push_back(sdpService);
        }
    }

    // Store device
    {
        std::lock_guard<std::mutex> lock(*devicesMutex);
        discoveredDevices->push_back(classicDevice);
    }

    // Callback
    if (deviceFoundCallback) {
        deviceFoundCallback(classicDevice);
    }
}

- (void)deviceInquiryComplete:(IOBluetoothDeviceInquiry*)sender
                        error:(IOReturn)error
                      aborted:(BOOL)aborted {
    if (inquiryCompleteCallback) {
        bool success = (error == kIOReturnSuccess && !aborted);
        std::string errorMsg;
        if (!success) {
            if (aborted) {
                errorMsg = "Inquiry aborted";
            } else {
                errorMsg = "Inquiry failed with error code: " + std::to_string(error);
            }
        }
        inquiryCompleteCallback(success, errorMsg);
    }
}

@end

// PIMPL implementation
namespace ClassicBluetooth {

class Scanner::Impl {
public:
    IOBluetoothDeviceInquiry *inquiry;
    BluetoothInquiryDelegate *delegate;
    std::vector<ClassicDevice> discovered_devices;
    std::mutex devices_mutex;
    DeviceFoundCallback device_found_callback;
    InquiryCompleteCallback inquiry_complete_callback;
    bool is_scanning;

    Impl() : inquiry(nil), delegate(nil), is_scanning(false) {
        @autoreleasepool {
            delegate = [[BluetoothInquiryDelegate alloc]
                initWithCallbacks:nullptr
                       completeCb:nullptr
                          devices:&discovered_devices
                            mutex:&devices_mutex];

            inquiry = [[IOBluetoothDeviceInquiry alloc] initWithDelegate:delegate];
        }
    }

    ~Impl() {
        @autoreleasepool {
            if (inquiry) {
                [inquiry stop];
                inquiry = nil;
            }
            delegate = nil;
        }
    }
};

// Scanner implementation
Scanner::Scanner() : pImpl(new Impl()) {}

Scanner::~Scanner() {
    delete pImpl;
}

bool Scanner::start_inquiry(int duration_seconds) {
    @autoreleasepool {
        if (pImpl->is_scanning) {
            return false; // Already scanning
        }

        // Clear previous results
        {
            std::lock_guard<std::mutex> lock(pImpl->devices_mutex);
            pImpl->discovered_devices.clear();
        }

        [pImpl->inquiry setInquiryLength:duration_seconds];
        [pImpl->inquiry setUpdateNewDeviceNames:YES];

        IOReturn result = [pImpl->inquiry start];
        if (result == kIOReturnSuccess) {
            pImpl->is_scanning = true;
            return true;
        }
        return false;
    }
}

void Scanner::stop_inquiry() {
    @autoreleasepool {
        if (pImpl->inquiry) {
            [pImpl->inquiry stop];
            pImpl->is_scanning = false;
        }
    }
}

bool Scanner::is_scanning() const {
    return pImpl->is_scanning;
}

void Scanner::set_device_found_callback(DeviceFoundCallback callback) {
    pImpl->device_found_callback = callback;

    @autoreleasepool {
        pImpl->delegate = [[BluetoothInquiryDelegate alloc]
            initWithCallbacks:callback
                   completeCb:pImpl->inquiry_complete_callback
                      devices:&pImpl->discovered_devices
                        mutex:&pImpl->devices_mutex];
        [pImpl->inquiry setDelegate:pImpl->delegate];
    }
}

void Scanner::set_inquiry_complete_callback(InquiryCompleteCallback callback) {
    pImpl->inquiry_complete_callback = callback;

    @autoreleasepool {
        pImpl->delegate = [[BluetoothInquiryDelegate alloc]
            initWithCallbacks:pImpl->device_found_callback
                   completeCb:callback
                      devices:&pImpl->discovered_devices
                        mutex:&pImpl->devices_mutex];
        [pImpl->inquiry setDelegate:pImpl->delegate];
    }
}

std::vector<ClassicDevice> Scanner::get_discovered_devices() {
    std::lock_guard<std::mutex> lock(pImpl->devices_mutex);
    return pImpl->discovered_devices;
}

std::vector<SDPService> Scanner::query_sdp_services(const std::string& address) {
    std::vector<SDPService> services;

    @autoreleasepool {
        NSString *addressStr = [NSString stringWithUTF8String:address.c_str()];
        IOBluetoothDevice *device = [IOBluetoothDevice deviceWithAddressString:addressStr];

        if (!device) {
            return services;
        }

        // Perform SDP query
        IOReturn result = [device performSDPQuery:nil];
        if (result != kIOReturnSuccess) {
            return services;
        }

        NSArray *sdpRecords = [device services];
        if (!sdpRecords) {
            return services;
        }

        for (IOBluetoothSDPServiceRecord *record in sdpRecords) {
            SDPService service;

            // Service name
            NSString *serviceName = [record getServiceName];
            if (serviceName) {
                service.name = [serviceName UTF8String];
            }

            // RFCOMM channel
            BluetoothRFCOMMChannelID channelID;
            if ([record getRFCOMMChannelID:&channelID] == kIOReturnSuccess) {
                service.rfcomm_channel = channelID;
            }

            // Try to get service UUID
            BluetoothSDPServiceRecordHandle handle;
            if ([record getServiceRecordHandle:&handle] == kIOReturnSuccess) {
                char uuidStr[10];
                snprintf(uuidStr, sizeof(uuidStr), "%04X", (uint16_t)handle);
                service.uuid = uuidStr;
                service.name = decode_service_uuid(service.uuid);
            }

            services.push_back(service);
        }
    }

    return services;
}

DeviceClass Scanner::get_device_class(const std::string& address) {
    DeviceClass devClass;

    @autoreleasepool {
        NSString *addressStr = [NSString stringWithUTF8String:address.c_str()];
        IOBluetoothDevice *device = [IOBluetoothDevice deviceWithAddressString:addressStr];

        if (device) {
            BluetoothClassOfDevice cod = [device classOfDevice];
            devClass.class_of_device = cod;
            devClass.major_class = decode_device_class_major(cod);
            devClass.minor_class = decode_device_class_minor(cod);
        }
    }

    return devClass;
}

// Utility functions
std::string decode_device_class_major(uint32_t cod) {
    uint8_t major = (cod >> 8) & 0x1F;

    switch (major) {
        case 0x00: return "Miscellaneous";
        case 0x01: return "Computer";
        case 0x02: return "Phone";
        case 0x03: return "LAN/Network Access Point";
        case 0x04: return "Audio/Video";
        case 0x05: return "Peripheral";
        case 0x06: return "Imaging";
        case 0x07: return "Wearable";
        case 0x08: return "Toy";
        case 0x09: return "Health";
        case 0x1F: return "Uncategorized";
        default: return "Unknown";
    }
}

std::string decode_device_class_minor(uint32_t cod) {
    uint8_t major = (cod >> 8) & 0x1F;
    uint8_t minor = (cod >> 2) & 0x3F;

    // Computer minor classes
    if (major == 0x01) {
        switch (minor) {
            case 0x00: return "Uncategorized";
            case 0x01: return "Desktop Workstation";
            case 0x02: return "Server-class Computer";
            case 0x03: return "Laptop";
            case 0x04: return "Handheld PC/PDA";
            case 0x05: return "Palm-size PC/PDA";
            case 0x06: return "Wearable Computer";
            case 0x07: return "Tablet";
            default: return "Unknown Computer";
        }
    }

    // Phone minor classes
    if (major == 0x02) {
        switch (minor) {
            case 0x00: return "Uncategorized";
            case 0x01: return "Cellular";
            case 0x02: return "Cordless";
            case 0x03: return "Smartphone";
            case 0x04: return "Wired Modem/Voice Gateway";
            case 0x05: return "Common ISDN Access";
            default: return "Unknown Phone";
        }
    }

    // Audio/Video minor classes
    if (major == 0x04) {
        switch (minor) {
            case 0x00: return "Uncategorized";
            case 0x01: return "Wearable Headset";
            case 0x02: return "Hands-free";
            case 0x04: return "Microphone";
            case 0x05: return "Loudspeaker";
            case 0x06: return "Headphones";
            case 0x07: return "Portable Audio";
            case 0x08: return "Car Audio";
            case 0x09: return "Set-top Box";
            case 0x0A: return "HiFi Audio";
            case 0x0B: return "VCR";
            case 0x0C: return "Video Camera";
            case 0x0D: return "Camcorder";
            case 0x0E: return "Video Monitor";
            case 0x0F: return "Video Display and Loudspeaker";
            case 0x10: return "Video Conferencing";
            case 0x12: return "Gaming/Toy";
            default: return "Unknown Audio/Video";
        }
    }

    return "Unknown";
}

std::string decode_service_uuid(const std::string& uuid) {
    static const std::map<std::string, std::string> SERVICE_MAP = {
        {"1101", "Serial Port (SPP)"},
        {"1102", "LAN Access Using PPP"},
        {"1103", "Dialup Networking (DUN)"},
        {"1104", "IrMC Sync"},
        {"1105", "OBEX Object Push"},
        {"1106", "OBEX File Transfer"},
        {"1107", "IrMC Sync Command"},
        {"1108", "Headset"},
        {"1109", "Cordless Telephony"},
        {"110A", "Audio Source"},
        {"110B", "Audio Sink"},
        {"110C", "A/V Remote Control Target"},
        {"110D", "Advanced Audio Distribution"},
        {"110E", "A/V Remote Control"},
        {"110F", "A/V Remote Control Controller"},
        {"1112", "Headset Audio Gateway"},
        {"1115", "PANU"},
        {"1116", "NAP"},
        {"1117", "GN"},
        {"1118", "Direct Printing"},
        {"111E", "Handsfree"},
        {"111F", "Handsfree Audio Gateway"},
        {"1200", "PnP Information"},
        {"1201", "Generic Networking"},
        {"1202", "Generic File Transfer"},
        {"1203", "Generic Audio"},
        {"1204", "Generic Telephony"},
        {"1812", "Human Interface Device (HID)"}
    };

    auto it = SERVICE_MAP.find(uuid);
    if (it != SERVICE_MAP.end()) {
        return it->second;
    }
    return "Unknown Service (" + uuid + ")";
}

} // namespace ClassicBluetooth
