# #region EncryptionCore [C:5] [TYPE Module] [SEMANTICS encryption, fernet, key, crypto, secret]
# @BRIEF Core encryption infrastructure: Fernet-based EncryptionManager for reversible secret storage.
# @LAYER Infrastructure
# @RELATION DEPENDS_ON -> [LoggerModule]
# @INVARIANT Encryption initialization never falls back to a hardcoded secret.
#            ENCRYPTION_KEY must be set via environment or .env before first use.
# @PRE Runtime environment has a valid Fernet ENCRYPTION_KEY.
# @POST EncryptionManager provides encrypt/decrypt operations backed by validated Fernet key.
# @SIDE_EFFECT Reads ENCRYPTION_KEY from process environment.
# @DATA_CONTRACT Input[str] -> Encrypted[str] -> Output[str]
# @RATIONALE Extracted from services/llm_provider.py to decouple core encryption from LLM domain
#            and enable reuse by ConfigManager for Superset password encryption (see [SEC:C-1]).

import os

from cryptography.fernet import Fernet

from .logger import belief_scope, logger


# #region _require_fernet_key [C:5] [TYPE Function]
# @BRIEF Load and validate the Fernet key used for secret encryption.
# @PRE  ENCRYPTION_KEY environment variable must be set to a valid Fernet key.
# @POST  Returns validated key bytes ready for Fernet initialization.
# @RELATION DEPENDS_ON -> [LoggerModule]
# @SIDE_EFFECT  Emits belief-state logs for missing or invalid encryption configuration.
# @DATA_CONTRACT  Input[ENCRYPTION_KEY:str] -> Output[bytes]
# @INVARIANT  Encryption initialization never falls back to a hardcoded secret.
def _require_fernet_key() -> bytes:
    with belief_scope("_require_fernet_key"):
        raw_key = os.getenv("ENCRYPTION_KEY", "").strip()
        if not raw_key:
            logger.explore(
                "Missing ENCRYPTION_KEY blocks EncryptionManager initialization"
            )
            raise RuntimeError(
                "ENCRYPTION_KEY must be set. Run ensure_encryption_key() "
                "during application startup before using EncryptionManager."
            )

        key = raw_key.encode()
        try:
            Fernet(key)
        except Exception as exc:
            logger.explore(
                "Invalid ENCRYPTION_KEY blocks EncryptionManager initialization"
            )
            raise RuntimeError("ENCRYPTION_KEY must be a valid Fernet key") from exc

        logger.reflect("Validated ENCRYPTION_KEY for EncryptionManager initialization")
        return key


# #endregion _require_fernet_key


# #region EncryptionManager [C:5] [TYPE Class]
# @BRIEF Handles encryption and decryption of sensitive data like API keys and passwords.
# @RELATION CALLS -> [_require_fernet_key]
# @PRE  ENCRYPTION_KEY is configured with a valid Fernet key before instantiation.
# @POST  Manager exposes reversible encrypt/decrypt operations for persisted secrets.
# @SIDE_EFFECT  Initializes Fernet cryptography state from process environment.
# @DATA_CONTRACT  Input[str] -> Output[str]
# @INVARIANT  Uses only a validated secret key from environment.
#
# @TEST_CONTRACT  EncryptionManagerModel ->
# {
#   required_fields: {},
#   invariants: [
#       "encrypted data can be decrypted back to the original string"
#   ]
# }
# @TEST_FIXTURE  basic_encryption_cycle -> {"data": "my_secret_key"}
# @TEST_EDGE  decrypt_invalid_data -> raises Exception
# @TEST_EDGE  empty_string_encryption -> {"data": ""}
# @TEST_INVARIANT  symmetric_encryption -> verifies: [basic_encryption_cycle, empty_string_encryption]
class EncryptionManager:
    # region EncryptionManager_init [TYPE Function]
    # @PURPOSE: Initialize the encryption manager with a Fernet key.
    # @PRE  ENCRYPTION_KEY env var must be set to a valid Fernet key.
    # @POST  Fernet instance ready for encryption/decryption.
    def __init__(self):
        self.key = _require_fernet_key()
        self.fernet = Fernet(self.key)

    # endregion EncryptionManager_init

    # region encrypt [TYPE Function]
    # @PURPOSE: Encrypt a plaintext string.
    # @PRE  data must be a non-empty string.
    # @POST  Returns encrypted string.
    def encrypt(self, data: str) -> str:
        with belief_scope("encrypt"):
            return self.fernet.encrypt(data.encode()).decode()

    # endregion encrypt

    # region decrypt [TYPE Function]
    # @PURPOSE: Decrypt an encrypted string.
    # @PRE  encrypted_data must be a valid Fernet-encrypted string.
    # @POST  Returns original plaintext string.
    def decrypt(self, encrypted_data: str) -> str:
        with belief_scope("decrypt"):
            return self.fernet.decrypt(encrypted_data.encode()).decode()

    # endregion decrypt


# #endregion EncryptionManager
# #endregion EncryptionCore
