"""
vault_client.py
===============
Reusable vault credential retrieval for all SKG pipeline scripts.
Place this file in VAULT_DIR alongside vault.py.

Reads from environment (load_dotenv must be called before importing):
  OS env vars : VAULT_DIR  - folder containing vault.py
                VPATH      - path to vault.enc
                ID_ENC     - AES-encrypted master password
  .env file   : VAULT_KEY  - Fernet key that decrypts ID_ENC

Public API:
    from vault_client import get_vault_credentials, get_wallet_password

    db_user, db_password   = get_vault_credentials('MULBUILDDB1')
    wallet_pw              = get_wallet_password('C:/code/wallet/Wallet_MULBUILDDB1')

Author: M Dombaugh
Version: 1.0 - Initial implementation with encrypted master password support.
               Fernet symmetric encryption: ID_ENC (OS env) decrypted by
               VAULT_KEY (.env). Falls back to plain ID env var for
               backward compatibility.
"""

import os
import sys
from pathlib import Path

# ---------------------------------------------------------------------------
# Internal: locate and import SecureVault
# ---------------------------------------------------------------------------

def _import_secure_vault():
    """
    Attempt to import SecureVault. Searches VAULT_DIR env var first,
    then a small set of conventional fallback locations.
    Returns SecureVault class or None.
    """
    try:
        from vault import SecureVault
        return SecureVault
    except ImportError:
        pass

    search_paths = []
    vault_dir_env = os.getenv("VAULT_DIR")
    if vault_dir_env:
        search_paths.append(Path(vault_dir_env))
    search_paths.extend([
        Path.home() / ".secure_vault",
        Path("C:/Oracle/vault"),
        Path.cwd() / "vault",
    ])

    for folder in search_paths:
        if folder.exists() and str(folder) not in sys.path:
            sys.path.insert(0, str(folder))
            try:
                from vault import SecureVault
                return SecureVault
            except ImportError:
                sys.path.pop(0)

    return None


# ---------------------------------------------------------------------------
# Internal: decrypt master password
# ---------------------------------------------------------------------------

def _get_master_password():
    """
    Decrypt master password from ID_ENC (OS env) using VAULT_KEY (.env).
    Falls back to plain ID env var for backward compatibility.
    Returns decrypted master password string or None.
    """
    vault_key = os.getenv("VAULT_KEY")
    id_enc    = os.getenv("ID_ENC")

    if vault_key and id_enc:
        try:
            from cryptography.fernet import Fernet, InvalidToken
            key = vault_key.encode() if isinstance(vault_key, str) else vault_key
            token = id_enc.encode() if isinstance(id_enc, str) else id_enc
            return Fernet(key).decrypt(token).decode()
        except Exception as e:
            print(f"[VAULT] Failed to decrypt master password: {e}")
            return None

    # Backward compatibility: plain master password in ID
    plain = os.getenv("ID")
    if plain:
        return plain

    print("[VAULT] No master password available. Set ID_ENC + VAULT_KEY, or ID.")
    return None


# ---------------------------------------------------------------------------
# Internal: open vault and retrieve a service entry
# ---------------------------------------------------------------------------

def _get_from_vault(service_name, username=None):
    """
    Open vault, retrieve credentials for service_name.
    Returns the entry dict {username, password, notes, ...} or None.
    """
    SecureVault = _import_secure_vault()
    if SecureVault is None:
        print("[VAULT] vault.py not found. Set VAULT_DIR correctly.")
        return None

    vpath = os.getenv("VPATH")
    if not vpath or not Path(vpath).exists():
        print(f"[VAULT] Vault file not found at VPATH='{vpath}'")
        return None

    master_password = _get_master_password()
    if not master_password:
        return None

    try:
        vault = SecureVault(vpath)
        if not vault.unlock_vault(master_password):
            print("[VAULT] Failed to unlock vault.")
            return None

        service_entries = vault.get_password(service_name, username)
        vault.lock_vault()

        if service_entries is None:
            print(f"[VAULT] Service '{service_name}' not found in vault.")
            return None

        if username:
            # get_password returns single entry when username specified
            return service_entries

        # No username specified - return first entry
        return next(iter(service_entries.values()))

    except Exception as e:
        print(f"[VAULT] Error accessing vault: {e}")
        return None


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------

def get_vault_credentials(service_name, username=None):
    """
    Retrieve database credentials from the vault.

    Args:
        service_name : Vault service key, derived from wallet folder name
                       e.g. 'MULBUILDDB1' from Wallet_MULBUILDDB1
        username     : Optional - retrieve specific username entry.
                       If omitted, returns first entry for the service.

    Returns:
        (username, password) tuple, or (None, None) on failure.

    Example:
        db_user, db_pass = get_vault_credentials('MULBUILDDB1')
    """
    entry = _get_from_vault(service_name, username)
    if entry:
        return entry.get("username"), entry.get("password")
    return None, None


def get_wallet_password(oracle_wallet_path):
    """
    Retrieve wallet password from the vault.

    Derives the vault service name from the wallet folder name by
    appending '_WALLET'. e.g. Wallet_MULBUILDDB1 -> MULBUILDDB1_WALLET

    Args:
        oracle_wallet_path : Path to the wallet folder (string or Path).
                             Matches ORACLE_WALLET_PATH in .env.

    Returns:
        Wallet password string, or None on failure.

    Example:
        wallet_pw = get_wallet_password('C:/code/wallet/Wallet_MULBUILDDB1')
    """
    wallet_folder  = Path(oracle_wallet_path).name
    service_name   = (
        wallet_folder
        .replace("Wallet_", "")
        .replace("wallet_", "")
        .strip()
        + "_WALLET"
    )
    entry = _get_from_vault(service_name)
    if entry:
        return entry.get("password")
    print(f"[VAULT] No wallet password found for service: {service_name}")
    return None


def get_service_name_from_wallet_path(oracle_wallet_path):
    """
    Utility: derive the vault service name from a wallet folder path.
    e.g. 'C:/code/wallet/Wallet_MULBUILDDB1' -> 'MULBUILDDB1'
    """
    wallet_folder = Path(oracle_wallet_path).name
    return (
        wallet_folder
        .replace("Wallet_", "")
        .replace("wallet_", "")
        .strip()
    )
