"""Win32 Core Audio helpers for microphone volume control.

Uses the Windows MMDevice / IAudioEndpointVolume COM interfaces via ctypes
to force the microphone capture level to 100% before recording.
No external dependencies required (no pycaw/comtypes).
"""
import ctypes
import ctypes.wintypes
from ctypes import HRESULT, POINTER, byref

from loguru import logger

# ─── COM GUID ─────────────────────────────────────────────────────────────

class GUID(ctypes.Structure):
    _fields_ = [
        ("Data1", ctypes.c_ulong),
        ("Data2", ctypes.c_ushort),
        ("Data3", ctypes.c_ushort),
        ("Data4", ctypes.c_ubyte * 8),
    ]


def _guid(s: str) -> GUID:
    s = s.strip("{}")
    p = s.split("-")
    d4 = bytes.fromhex(p[3] + p[4])
    return GUID(int(p[0], 16), int(p[1], 16), int(p[2], 16), (ctypes.c_ubyte * 8)(*d4))


CLSID_MMDeviceEnumerator = _guid("{BCDE0395-E52F-467C-8E3D-C4579291692E}")
IID_IMMDeviceEnumerator  = _guid("{A95664D2-9614-4F35-A746-DE8DB63617E6}")
IID_IAudioEndpointVolume = _guid("{5CDF2C82-841E-4546-9722-0CF74078229A}")

eCapture = 1   # EDataFlow::eCapture
eConsole = 0   # ERole::eConsole
CLSCTX_ALL = 0x17
COINIT_APARTMENTTHREADED = 2  # Core Audio requires STA
RPC_E_CHANGED_MODE = -2147417850  # 0x80010106: thread already in another apartment

_ole32 = ctypes.windll.ole32
_ole32.CoInitializeEx.restype = HRESULT
_ole32.CoInitializeEx.argtypes = [ctypes.c_void_p, ctypes.c_ulong]
_ole32.CoCreateInstance.restype = HRESULT
_ole32.CoCreateInstance.argtypes = [
    POINTER(GUID), ctypes.c_void_p, ctypes.c_ulong,
    POINTER(GUID), POINTER(ctypes.c_void_p),
]

# ─── Typed COM method signatures ─────────────────────────────────────────

# IMMDeviceEnumerator::GetDefaultAudioEndpoint (vtable index 4)
_GetDefaultAudioEndpoint_t = ctypes.CFUNCTYPE(
    HRESULT, ctypes.c_void_p,
    ctypes.c_uint, ctypes.c_uint, POINTER(ctypes.c_void_p),
)

# IMMDevice::Activate (vtable index 3)
_Activate_t = ctypes.CFUNCTYPE(
    HRESULT, ctypes.c_void_p,
    POINTER(GUID), ctypes.c_ulong, ctypes.c_void_p, POINTER(ctypes.c_void_p),
)

# IAudioEndpointVolume::SetMasterVolumeLevelScalar (vtable index 7)
_SetVolume_t = ctypes.CFUNCTYPE(
    HRESULT, ctypes.c_void_p, ctypes.c_float, ctypes.c_void_p,
)

# IAudioEndpointVolume::SetMute (vtable index 14)
_SetMute_t = ctypes.CFUNCTYPE(
    HRESULT, ctypes.c_void_p, ctypes.c_int, ctypes.c_void_p,
)


def _get_vtable(ptr: ctypes.c_void_p):
    """Dereference COM interface pointer → vtable pointer array."""
    return ctypes.cast(
        ctypes.cast(ptr, POINTER(ctypes.c_void_p))[0],
        POINTER(ctypes.c_void_p),
    )


def _release(ptr: ctypes.c_void_p) -> None:
    """IUnknown::Release (vtable index 2)."""
    if ptr and ptr.value:
        try:
            vt = _get_vtable(ptr)
            fn = ctypes.cast(vt[2], ctypes.CFUNCTYPE(ctypes.c_ulong, ctypes.c_void_p))
            fn(ptr)
        except Exception:
            pass


# ─── Public API ───────────────────────────────────────────────────────────

def set_mic_volume(level: float = 1.0) -> bool:
    """Set the default capture device (microphone) master volume.

    Args:
        level: Volume scalar from 0.0 (mute) to 1.0 (100%).

    Returns:
        True if volume was set successfully, False otherwise.
    """
    level = max(0.0, min(1.0, level))
    enumerator = ctypes.c_void_p()
    device = ctypes.c_void_p()
    endpoint_vol = ctypes.c_void_p()
    com_initialized = False

    try:
        # Prefer STA for Core Audio. On a worker thread that is already in a
        # different apartment (e.g. MTA from the comtypes prewarm) this raises
        # RPC_E_CHANGED_MODE — the MMDevice APIs still work there, so keep
        # going with the existing apartment instead of failing the call.
        try:
            hr = _ole32.CoInitializeEx(None, COINIT_APARTMENTTHREADED)
            com_initialized = hr >= 0
        except OSError as e:
            if e.winerror != RPC_E_CHANGED_MODE:
                raise
            com_initialized = False

        # Create MMDeviceEnumerator
        hr = _ole32.CoCreateInstance(
            byref(CLSID_MMDeviceEnumerator), None, CLSCTX_ALL,
            byref(IID_IMMDeviceEnumerator), byref(enumerator),
        )
        if hr < 0:
            logger.debug("CoCreateInstance(MMDeviceEnumerator) failed: 0x{:08X}", hr & 0xFFFFFFFF)
            return False

        # GetDefaultAudioEndpoint(eCapture, eConsole) → device
        vt = _get_vtable(enumerator)
        fn = ctypes.cast(vt[4], _GetDefaultAudioEndpoint_t)
        hr = fn(enumerator, eCapture, eConsole, byref(device))
        if hr < 0:
            logger.debug("GetDefaultAudioEndpoint failed: 0x{:08X}", hr & 0xFFFFFFFF)
            return False

        # device.Activate(IAudioEndpointVolume) → endpoint_vol
        vt = _get_vtable(device)
        fn = ctypes.cast(vt[3], _Activate_t)
        hr = fn(device, byref(IID_IAudioEndpointVolume), CLSCTX_ALL, None, byref(endpoint_vol))
        if hr < 0:
            logger.debug("IMMDevice::Activate failed: 0x{:08X}", hr & 0xFFFFFFFF)
            return False

        vt = _get_vtable(endpoint_vol)

        # SetMasterVolumeLevelScalar(level, NULL) — vtable index 7
        fn_vol = ctypes.cast(vt[7], _SetVolume_t)
        hr = fn_vol(endpoint_vol, level, None)
        if hr < 0:
            logger.debug("SetMasterVolumeLevelScalar failed: 0x{:08X}", hr & 0xFFFFFFFF)
            return False

        # SetMute(FALSE, NULL) — vtable index 14
        fn_mute = ctypes.cast(vt[14], _SetMute_t)
        fn_mute(endpoint_vol, 0, None)

        logger.info("Microphone volume set to {:.0%}", level)
        return True

    except Exception as e:
        logger.warning("Failed to set microphone volume: {}", e)
        return False
    finally:
        _release(endpoint_vol)
        _release(device)
        _release(enumerator)
        if com_initialized:
            try:
                _ole32.CoUninitialize()
            except Exception:
                pass
