"""
DPAPI Password Decryption Module
=================================

This module provides functionality to decrypt passwords encrypted using
Windows Data Protection API (DPAPI).
"""

import os
from typing import Optional


def decrypt_dpapi_password(password_file: str) -> str:
    """
    Decrypt a password file encrypted with Windows DPAPI.

    This function reads a file that was encrypted using Windows DPAPI
    (like those created by Save-EncryptedPassword.ps1) and returns
    the decrypted plaintext password.

    Args:
        password_file: Path to the encrypted password file

    Returns:
        str: The decrypted password in plaintext

    Raises:
        FileNotFoundError: If the password file doesn't exist
        ValueError: If decryption fails or the file is invalid

    Note:
        DPAPI encryption is tied to the computer and user account that
        encrypted it. The file can only be decrypted on the same computer
        by the same user (or with appropriate DPAPI master keys).
    """
    if not os.path.exists(password_file):
        raise FileNotFoundError(f"Password file not found: {password_file}")

    try:
        # Read the encrypted bytes
        with open(password_file, 'rb') as f:
            encrypted_bytes = f.read()

        # Try to decrypt using Windows DPAPI via ctypes
        # This requires the Windows Crypt32.dll
        import ctypes
        from ctypes import wintypes

        # Load the Crypt32 library
        crypt32 = ctypes.windll.crypt32

        # Define the CryptUnprotectData function
        class DATA_BLOB(ctypes.Structure):
            _fields_ = [
                ("cbData", wintypes.DWORD),
                ("pbData", ctypes.POINTER(ctypes.c_byte))
            ]

        crypt_unprotect_data = crypt32.CryptUnprotectData
        crypt_unprotect_data.argtypes = [
            ctypes.POINTER(DATA_BLOB),  # pDataIn
            ctypes.c_wchar_p,           # ppszDataDescr
            ctypes.POINTER(DATA_BLOB),  # pOptionalEntropy
            ctypes.c_void_p,            # pvReserved
            ctypes.c_void_p,            # pPromptStruct
            wintypes.DWORD              # dwFlags
        ]
        crypt_unprotect_data.restype = wintypes.BOOL

        # Create input blob
        input_blob = DATA_BLOB()
        input_blob.cbData = len(encrypted_bytes)
        input_blob.pbData = (ctypes.c_byte * len(encrypted_bytes))(*encrypted_bytes)

        # Create output blob
        output_blob = DATA_BLOB()

        # Decrypt
        result = crypt_unprotect_data(
            ctypes.byref(input_blob),
            None,
            None,
            None,
            None,
            0
        )

        if not result:
            error_code = ctypes.get_last_error()
            raise ValueError(
                f"DPAPI decryption failed with error code: {error_code}. "
                "Make sure you're running on the same Windows computer and user account "
                "that encrypted the password."
            )

        # Extract decrypted data
        decrypted_bytes = bytearray(output_blob.pbData[i] for i in range(output_blob.cbData))

        # Convert to string
        password = decrypted_bytes.decode('utf-8')

        # LocalFree is needed to free the memory allocated by CryptUnprotectData
        ctypes.windll.kernel32.LocalFree(output_blob.pbData)

        return password

    except Exception as e:
        if "Decrypt" in str(type(e)):
            # Fallback error message for dpapi package usage
            raise ValueError(
                f"Failed to decrypt password file: {e}. "
                "Ensure the file was encrypted on this computer by this user account."
            )
        raise ValueError(f"Error decrypting password file: {e}")


def decrypt_dpapi_password_fallback(password_file: str) -> str:
    """
    Alternative method using dpapi Python package if available.

    Args:
        password_file: Path to the encrypted password file

    Returns:
        str: The decrypted password in plaintext

    Raises:
        ImportError: If dpapi package is not installed
        ValueError: If decryption fails
    """
    try:
        import dpapi

        with open(password_file, 'rb') as f:
            encrypted_bytes = f.read()

        decrypted_bytes = dpapi.decrypt(encrypted_bytes)
        password = decrypted_bytes.decode('utf-8')
        return password

    except ImportError:
        raise ImportError(
            "The 'dpapi' package is not installed. "
            "Install it with: pip install dpapi"
        )
    except Exception as e:
        raise ValueError(f"Error decrypting password file with dpapi package: {e}")


if __name__ == "__main__":
    # Test the decryption module
    import sys

    if len(sys.argv) < 2:
        print("Usage: python dpapi_crypto.py <password_file>")
        sys.exit(1)

    password_file = sys.argv[1]

    try:
        password = decrypt_dpapi_password(password_file)
        print(f"Successfully decrypted password from: {password_file}")
        print(f"Password length: {len(password)} characters")
    except Exception as e:
        print(f"Error: {e}")
        sys.exit(1)
