// Standard library includes
#include <future>
#include <string>
#include <sstream>
#include <iomanip>
#include <cstdint>

// Windows Runtime includes
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Devices.Bluetooth.h>
#include <winrt/Windows.Devices.Enumeration.h>
#include <winrt/Windows.Devices.Radios.h>

// Project includes
#include "radio_watcher.h"
#include "winrt_cpp.h"

using namespace winrt::Windows::Devices::Enumeration;
using namespace winrt::Windows::Devices::Bluetooth;
using namespace winrt::Windows::Devices::Bluetooth::GenericAttributeProfile;
using winrt::Windows::Devices::Radios::RadioState;

template <typename O, typename M, class... Types> auto bind2(O* object, M method, Types&... args)
{
    return std::bind(method, object, std::placeholders::_1, std::placeholders::_2, args...);
}

const char* adapterStateToString(AdapterState state)
{
    switch (state)
    {
    case AdapterState::Unsupported:
        return "unsupported";
    case AdapterState::On:
        return "poweredOn";
        break;
    case AdapterState::Off:
        return "poweredOff";
        break;
    case AdapterState::Disabled:
        return "poweredOff";
        break;
    default:
        return "unknown";
    }
}

RadioWatcher::RadioWatcher()
    : mRadio(nullptr),
      watcher(DeviceInformation::CreateWatcher(BluetoothAdapter::GetDeviceSelector())),
      mAlive(std::make_shared<std::atomic<bool>>(true))
{
    mAddedRevoker = watcher.Added(winrt::auto_revoke, bind2(this, &RadioWatcher::OnAdded));
    mUpdatedRevoker = watcher.Updated(winrt::auto_revoke, bind2(this, &RadioWatcher::OnUpdated));
    mRemovedRevoker = watcher.Removed(winrt::auto_revoke, bind2(this, &RadioWatcher::OnRemoved));
    auto completed = bind2(this, &RadioWatcher::OnCompleted);
    mCompletedRevoker = watcher.EnumerationCompleted(winrt::auto_revoke, completed);
}

RadioWatcher::~RadioWatcher()
{
    // Signal any in-flight OnRadioChanged() coroutine that this object is gone
    // so it does not dereference `this` after resuming from a co_await. Without
    // this, tearing down the BLEManager (noble.stop()) while an enumeration /
    // radio-state coroutine is still pending races into a use-after-free and
    // crashes the process with 0xC0000005 (ACCESS_VIOLATION).
    if (mAlive)
    {
        mAlive->store(false);
    }

    // Stop delivering further callbacks before the members they touch are
    // destroyed.
    mAddedRevoker.revoke();
    mUpdatedRevoker.revoke();
    mRemovedRevoker.revoke();
    mCompletedRevoker.revoke();
    mRadioStateChangedRevoker.revoke();

    try
    {
        if (watcher)
        {
            auto status = watcher.Status();
            if (status == DeviceWatcherStatus::Started ||
                status == DeviceWatcherStatus::EnumerationCompleted)
            {
                watcher.Stop();
            }
        }
    }
    catch (...)
    {
        // Best effort during teardown; never let an exception escape a destructor.
    }
}

void RadioWatcher::Start(std::function<void(Radio& radio, const AdapterCapabilities& capabilities)> on)
{
    radioStateChanged = on;
    inEnumeration = true;
    watcher.Start();
}

winrt::fire_and_forget RadioWatcher::OnRadioChanged() {
    // Keep a private copy of the liveness flag alive for the whole coroutine so
    // it stays valid even if the RadioWatcher is destroyed while we are
    // suspended on a co_await. Re-check it after every co_await before touching
    // `this` to avoid a use-after-free during teardown (noble.stop()).
    auto alive = mAlive;
    try {
        auto adapter = co_await BluetoothAdapter::GetDefaultAsync();
        if (!alive->load()) co_return;

        if (adapter) {
            auto radio = co_await adapter.GetRadioAsync();
            if (!alive->load()) co_return;

            AdapterCapabilities capabilities = {};
            capabilities.bluetoothAddress = adapter.BluetoothAddress();
            capabilities.classicSecureConnectionsSupported = adapter.AreClassicSecureConnectionsSupported();
            capabilities.lowEnergySecureConnectionsSupported = adapter.AreLowEnergySecureConnectionsSupported();
            capabilities.lowEnergySupported = adapter.IsLowEnergySupported();
            capabilities.peripheralRoleSupported = adapter.IsPeripheralRoleSupported();
            capabilities.centralRoleSupported = adapter.IsCentralRoleSupported();

            // IsExtendedAdvertisingSupported and MaxAdvertisementDataLength were
            // introduced in Windows 10, version 2004 (build 19041). On older builds
            // (e.g. 1909 / 18363) these accessors throw, which would otherwise abort
            // the whole capability read and make a working adapter look "unsupported".
            // Guard them separately so they simply fall back to conservative defaults.
            try
            {
                capabilities.extendedAdvertisingSupported = adapter.IsExtendedAdvertisingSupported();
                capabilities.maxAdvertisementDataLength = adapter.MaxAdvertisementDataLength();
            }
            catch (const winrt::hresult_error&)
            {
                capabilities.extendedAdvertisingSupported = false;
                capabilities.maxAdvertisementDataLength = 0;
            }

            Radio bluetooth = nullptr;
            // GetRadioAsync() can resolve to a null radio when the Bluetooth
            // Support Service (bthserv) is disabled, even though the adapter is
            // still enumerated. Guard against it so we don't call StateChanged()
            // on a null radio, which would otherwise crash the process.
            if (adapter.IsCentralRoleSupported() && radio)
            {
                // Always set up radio state monitoring for any radio (on or off)
                bluetooth = radio;
                mRadioStateChangedRevoker.revoke();
                mRadioStateChangedRevoker = radio.StateChanged(
                    winrt::auto_revoke, 
                    [this, capabilities](Radio radio, auto&&) { 
                        Radio bluetooth = radio;
                        radioStateChanged(bluetooth, capabilities); 
                    });
                
                radioStateChanged(bluetooth, capabilities);
                mRadio = bluetooth;
                
            }
            else
            {
                mRadioStateChangedRevoker.revoke();
                radioStateChanged(bluetooth, capabilities);
                mRadio = bluetooth;
            }
        } else {
            mRadio = nullptr;
            mRadioStateChangedRevoker.revoke();
            AdapterCapabilities emptyCapabilities = {};
            radioStateChanged(mRadio, emptyCapabilities);
        }
    } catch (...) {
        // OnRadioChanged() runs as a fire_and_forget coroutine: any exception
        // that escapes here terminates the whole process. Catch everything (not
        // just winrt::hresult_error) and degrade gracefully to an empty adapter
        // so a disabled bthserv / unexpected WinRT failure can't take down the app.
        if (!alive->load()) co_return;
        mRadio = nullptr;
        mRadioStateChangedRevoker.revoke();
        AdapterCapabilities emptyCapabilities = {};
        radioStateChanged(mRadio, emptyCapabilities);
    }
}

void RadioWatcher::OnAdded(DeviceWatcher watcher, DeviceInformation info)
{
    if (inEnumeration) { return; }
    OnRadioChanged();
}

void RadioWatcher::OnUpdated(DeviceWatcher watcher, DeviceInformationUpdate info)
{
    OnRadioChanged();
}

void RadioWatcher::OnRemoved(DeviceWatcher watcher, DeviceInformationUpdate info)
{
    OnRadioChanged();
}

void RadioWatcher::OnCompleted(DeviceWatcher watcher, IInspectable info)
{
    inEnumeration = false;
    OnRadioChanged();
}
