using System.Runtime.InteropServices;
using NexusShell.Models;

namespace NexusShell.Services;

public sealed class NexusAudioService
{
    public NexusAudioSnapshot Capture()
    {
        try
        {
            var deviceName = GetDefaultOutputDeviceName();
            var volume = GetMasterVolumePercent(out var canChange);
            return new NexusAudioSnapshot
            {
                OutputDevice = deviceName,
                VolumePercent = volume,
                CanChangeVolume = canChange,
            };
        }
        catch
        {
            return new NexusAudioSnapshot
            {
                OutputDevice = "Standard lydenhed",
                VolumePercent = 0,
                CanChangeVolume = false,
            };
        }
    }

    public bool TrySetVolumePercent(int percent)
    {
        percent = Math.Clamp(percent, 0, 100);
        try
        {
            var enumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
            enumerator.GetDefaultAudioEndpoint(EDataFlow.Render, ERole.Multimedia, out var device);
            var iid = typeof(IAudioEndpointVolume).GUID;
            device.Activate(ref iid, CLSCTX, IntPtr.Zero, out var obj);
            var volume = (IAudioEndpointVolume)obj!;
            volume.SetMasterVolumeLevelScalar(percent / 100f, Guid.Empty);
            Marshal.ReleaseComObject(volume);
            Marshal.ReleaseComObject(device);
            Marshal.ReleaseComObject(enumerator);
            return true;
        }
        catch
        {
            return false;
        }
    }

    private static string GetDefaultOutputDeviceName()
    {
        var enumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
        enumerator.GetDefaultAudioEndpoint(EDataFlow.Render, ERole.Multimedia, out var device);
        device.GetId(out _);
        device.OpenPropertyStore(StgmRead, out var store);
        var friendlyNameKey = PropertyKeyFriendlyName;
        store.GetValue(ref friendlyNameKey, out var value);
        var name = value.GetValue() as string;
        Marshal.ReleaseComObject(store);
        Marshal.ReleaseComObject(device);
        Marshal.ReleaseComObject(enumerator);
        return string.IsNullOrWhiteSpace(name) ? "Standard lydenhed" : name;
    }

    private static int GetMasterVolumePercent(out bool canChange)
    {
        canChange = false;
        var enumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
        enumerator.GetDefaultAudioEndpoint(EDataFlow.Render, ERole.Multimedia, out var device);
        var iid = typeof(IAudioEndpointVolume).GUID;
        device.Activate(ref iid, CLSCTX, IntPtr.Zero, out var obj);
        var volume = (IAudioEndpointVolume)obj!;
        volume.GetMasterVolumeLevelScalar(out var scalar);
        volume.GetMute(out var muted);
        canChange = true;
        Marshal.ReleaseComObject(volume);
        Marshal.ReleaseComObject(device);
        Marshal.ReleaseComObject(enumerator);
        return muted ? 0 : (int)Math.Round(scalar * 100);
    }

    private static readonly PropertyKey PropertyKeyFriendlyName = new()
    {
        fmtid = new Guid("a45c254e-df1c-4efd-8020-67d146a850e0"),
        pid = 14,
    };

    private const int StgmRead = 0x00000000;
    private const int CLSCTX = 0x17;

    [ComImport]
    [Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
    private class MMDeviceEnumerator;

    [ComImport]
    [Guid("A95664D2-9614-4F35-A746-DE8DB63617E6")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IMMDeviceEnumerator
    {
        int NotImpl1();
        [PreserveSig]
        int GetDefaultAudioEndpoint(EDataFlow dataFlow, ERole role, out IMMDevice ppDevice);
    }

    [ComImport]
    [Guid("D666063F-1587-4E43-81F1-B948E807363F")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IMMDevice
    {
        [PreserveSig]
        int Activate(ref Guid iid, int dwClsCtx, IntPtr pActivationParams, [MarshalAs(UnmanagedType.IUnknown)] out object ppInterface);
        int OpenPropertyStore(int stgmAccess, out IPropertyStore ppProperties);
        int GetId([MarshalAs(UnmanagedType.LPWStr)] out string ppstrId);
    }

    [ComImport]
    [Guid("886D8EEB-8CF2-4446-8D02-CDBA1DBDCF99")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IPropertyStore
    {
        int GetCount(out int cProps);
        int GetAt(int iProp, out PropertyKey pkey);
        int GetValue(ref PropertyKey key, out PropVariant pv);
    }

    [ComImport]
    [Guid("5CDF2C82-841E-4546-9722-0CF74078229A")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    private interface IAudioEndpointVolume
    {
        int RegisterControlChangeNotify(IntPtr pNotify);
        int UnregisterControlChangeNotify(IntPtr pNotify);
        int GetChannelCount(out uint pnChannelCount);
        int SetMasterVolumeLevel(float fLevelDB, ref Guid pguidEventContext);
        int SetMasterVolumeLevelScalar(float fLevel, ref Guid pguidEventContext);
        int GetMasterVolumeLevel(out float pfLevelDB);
        int GetMasterVolumeLevelScalar(out float pfLevel);
        int SetChannelVolumeLevel(uint nChannel, float fLevelDB, ref Guid pguidEventContext);
        int SetChannelVolumeLevelScalar(uint nChannel, float fLevel, ref Guid pguidEventContext);
        int GetChannelVolumeLevel(uint nChannel, out float pfLevelDB);
        int GetChannelVolumeLevelScalar(uint nChannel, out float pfLevel);
        int SetMute([MarshalAs(UnmanagedType.Bool)] bool bMute, ref Guid pguidEventContext);
        int GetMute(out bool pbMute);
    }

    private enum EDataFlow
    {
        Render = 0,
    }

    private enum ERole
    {
        Multimedia = 1,
    }

    [StructLayout(LayoutKind.Sequential)]
    private struct PropertyKey
    {
        public Guid fmtid;
        public int pid;
    }

    [StructLayout(LayoutKind.Explicit)]
    private struct PropVariant
    {
        [FieldOffset(0)] public ushort vt;
        [FieldOffset(8)] public IntPtr ptr;

        public object? GetValue() => vt switch
        {
            31 => Marshal.PtrToStringUni(ptr),
            _ => null,
        };
    }
}
