"""Encrypted credential store — RSA + AES hybrid encryption.

Key pair stored in ~/.ievo/ (like SSH keys).
Credentials are encrypted with AES-256-GCM, the AES key is encrypted with RSA public key.
"""

from __future__ import annotations

import base64
import json
import os
import stat
from pathlib import Path

from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.primitives.ciphers.aead import AESGCM


def _private_key_path(home: Path) -> Path:
    return home / "ievo_key"


def _public_key_path(home: Path) -> Path:
    return home / "ievo_key.pub"


def _credentials_path(home: Path) -> Path:
    return home / "credentials.enc"


def ensure_keys(home: Path) -> None:
    """Generate RSA 4096 key pair if not present. Sets chmod 600 on private key."""
    priv_path = _private_key_path(home)
    pub_path = _public_key_path(home)

    if priv_path.exists() and pub_path.exists():
        return

    private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096)

    priv_pem = private_key.private_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PrivateFormat.PKCS8,
        encryption_algorithm=serialization.NoEncryption(),
    )
    priv_path.write_bytes(priv_pem)
    os.chmod(priv_path, stat.S_IRUSR | stat.S_IWUSR)  # 600

    pub_pem = private_key.public_key().public_bytes(
        encoding=serialization.Encoding.PEM,
        format=serialization.PublicFormat.SubjectPublicKeyInfo,
    )
    pub_path.write_bytes(pub_pem)


def _load_private_key(home: Path) -> rsa.RSAPrivateKey:
    priv_pem = _private_key_path(home).read_bytes()
    key = serialization.load_pem_private_key(priv_pem, password=None)
    if not isinstance(key, rsa.RSAPrivateKey):
        msg = "Expected RSA private key"
        raise TypeError(msg)
    return key


def _load_public_key(home: Path) -> rsa.RSAPublicKey:
    pub_pem = _public_key_path(home).read_bytes()
    key = serialization.load_pem_public_key(pub_pem)
    if not isinstance(key, rsa.RSAPublicKey):
        msg = "Expected RSA public key"
        raise TypeError(msg)
    return key


def _rsa_padding() -> padding.OAEP:
    return padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None,
    )


def encrypt_credentials(home: Path, data: dict[str, str]) -> None:
    """Encrypt credentials dict and write to credentials.enc.

    Hybrid scheme: AES-256-GCM encrypts data, RSA encrypts AES key.
    """
    public_key = _load_public_key(home)

    # Generate random AES key + nonce
    aes_key = AESGCM.generate_key(bit_length=256)
    nonce = os.urandom(12)

    # Encrypt data with AES
    plaintext = json.dumps(data).encode()
    aesgcm = AESGCM(aes_key)
    ciphertext = aesgcm.encrypt(nonce, plaintext, None)

    # Encrypt AES key with RSA
    encrypted_aes_key = public_key.encrypt(aes_key, _rsa_padding())

    # Pack into JSON envelope
    envelope = {
        "encrypted_key": base64.b64encode(encrypted_aes_key).decode(),
        "nonce": base64.b64encode(nonce).decode(),
        "data": base64.b64encode(ciphertext).decode(),
    }
    _credentials_path(home).write_text(json.dumps(envelope, indent=2) + "\n")


def decrypt_credentials(home: Path) -> dict[str, str]:
    """Read and decrypt credentials.enc. Returns empty dict if file doesn't exist."""
    cred_path = _credentials_path(home)
    if not cred_path.exists():
        return {}

    private_key = _load_private_key(home)
    envelope = json.loads(cred_path.read_text())

    encrypted_aes_key = base64.b64decode(envelope["encrypted_key"])
    nonce = base64.b64decode(envelope["nonce"])
    ciphertext = base64.b64decode(envelope["data"])

    # Decrypt AES key with RSA
    aes_key = private_key.decrypt(encrypted_aes_key, _rsa_padding())

    # Decrypt data with AES
    aesgcm = AESGCM(aes_key)
    plaintext = aesgcm.decrypt(nonce, ciphertext, None)

    return json.loads(plaintext)


def get_credential(home: Path, key: str) -> str | None:
    """Get a single credential value."""
    creds = decrypt_credentials(home)
    return creds.get(key)


def set_credential(home: Path, key: str, value: str) -> None:
    """Set a single credential value (encrypts entire store)."""
    creds = decrypt_credentials(home)
    creds[key] = value
    encrypt_credentials(home, creds)


def delete_credential(home: Path, key: str) -> bool:
    """Delete a credential. Returns True if key existed."""
    creds = decrypt_credentials(home)
    if key not in creds:
        return False
    del creds[key]
    encrypt_credentials(home, creds)
    return True


def list_credentials(home: Path) -> list[str]:
    """List credential keys (no values)."""
    return list(decrypt_credentials(home).keys())


def is_sensitive_key(key: str) -> bool:
    """Determine if a key should be stored as encrypted credential."""
    upper = key.upper()
    sensitive_prefixes = ("ANTHROPIC_", "GITHUB_", "SENTRY_", "AWS_", "OPENAI_")
    sensitive_words = ("TOKEN", "KEY", "SECRET", "PASSWORD", "CREDENTIAL", "PAT")
    return any(upper.startswith(p) for p in sensitive_prefixes) or any(
        w in upper for w in sensitive_words
    )
