"""
credentials - Secure storage for API keys via the Windows Credential Manager.

Keys are never written to settings.json (plaintext).  We use the Win32
Credential Manager (advapi32) directly via ctypes to avoid adding a `keyring`
dependency (which complicates the Nuitka build).  Each secret is stored as a
generic credential under the target name "VoxCast/<name>".
"""
from __future__ import annotations

import ctypes
from ctypes import wintypes

from loguru import logger

_advapi32 = ctypes.WinDLL("advapi32", use_last_error=True)

CRED_TYPE_GENERIC = 1
CRED_PERSIST_LOCAL_MACHINE = 2
_TARGET_PREFIX = "VoxCast/"


class _CREDENTIAL(ctypes.Structure):
    _fields_ = [
        ("Flags", wintypes.DWORD),
        ("Type", wintypes.DWORD),
        ("TargetName", wintypes.LPWSTR),
        ("Comment", wintypes.LPWSTR),
        ("LastWritten", wintypes.FILETIME),
        ("CredentialBlobSize", wintypes.DWORD),
        ("CredentialBlob", ctypes.POINTER(ctypes.c_byte)),
        ("Persist", wintypes.DWORD),
        ("AttributeCount", wintypes.DWORD),
        ("Attributes", ctypes.c_void_p),
        ("TargetAlias", wintypes.LPWSTR),
        ("UserName", wintypes.LPWSTR),
    ]


_advapi32.CredWriteW.argtypes = [ctypes.POINTER(_CREDENTIAL), wintypes.DWORD]
_advapi32.CredWriteW.restype = wintypes.BOOL
_advapi32.CredReadW.argtypes = [
    wintypes.LPCWSTR, wintypes.DWORD, wintypes.DWORD,
    ctypes.POINTER(ctypes.POINTER(_CREDENTIAL)),
]
_advapi32.CredReadW.restype = wintypes.BOOL
_advapi32.CredDeleteW.argtypes = [wintypes.LPCWSTR, wintypes.DWORD, wintypes.DWORD]
_advapi32.CredDeleteW.restype = wintypes.BOOL
_advapi32.CredFree.argtypes = [ctypes.c_void_p]
_advapi32.CredFree.restype = None


def _target(name: str) -> str:
    return f"{_TARGET_PREFIX}{name}"


def set_secret(name: str, secret: str) -> bool:
    """Store (or overwrite) a secret. Returns True on success."""
    blob = secret.encode("utf-16-le")
    buf = (ctypes.c_byte * len(blob)).from_buffer_copy(blob)
    cred = _CREDENTIAL()
    cred.Type = CRED_TYPE_GENERIC
    cred.TargetName = _target(name)
    cred.CredentialBlobSize = len(blob)
    cred.CredentialBlob = ctypes.cast(buf, ctypes.POINTER(ctypes.c_byte))
    cred.Persist = CRED_PERSIST_LOCAL_MACHINE
    cred.UserName = name
    if not _advapi32.CredWriteW(ctypes.byref(cred), 0):
        logger.error("CredWrite failed for {} (err={})", name, ctypes.get_last_error())
        return False
    return True


def get_secret(name: str) -> str | None:
    """Retrieve a secret, or None if not stored."""
    ptr = ctypes.POINTER(_CREDENTIAL)()
    if not _advapi32.CredReadW(_target(name), CRED_TYPE_GENERIC, 0, ctypes.byref(ptr)):
        return None
    try:
        cred = ptr.contents
        size = cred.CredentialBlobSize
        if not size or not cred.CredentialBlob:
            return ""
        raw = ctypes.string_at(cred.CredentialBlob, size)
        return raw.decode("utf-16-le", errors="ignore")
    finally:
        _advapi32.CredFree(ptr)


def delete_secret(name: str) -> bool:
    """Remove a stored secret. Returns True if it existed and was removed."""
    return bool(_advapi32.CredDeleteW(_target(name), CRED_TYPE_GENERIC, 0))


def has_secret(name: str) -> bool:
    return get_secret(name) is not None
