"""
Mail Credentials Service

Secure storage and retrieval of email account credentials using HashiCorp Vault.
Falls back to encrypted database storage in development.
"""

import os
import base64
import hashlib
import logging
from typing import Optional, Dict
from dataclasses import dataclass
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

logger = logging.getLogger(__name__)

# Environment
ENVIRONMENT = os.getenv("ENVIRONMENT", "development")
VAULT_ADDR = os.getenv("VAULT_ADDR", "")
MAIL_CREDENTIALS_DIR = os.getenv("MAIL_CREDENTIALS_DIR", os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data", "mail_credentials"))


@dataclass
class MailCredentials:
    """Mail account credentials."""
    email: str
    password: str
    imap_host: str
    imap_port: int
    smtp_host: str
    smtp_port: int


class MailCredentialsService:
    """
    Service for storing and retrieving mail credentials securely.

    In production: Uses HashiCorp Vault KV v2
    In development: Uses Fernet encryption with a derived key
    """

    def __init__(self):
        self._vault_client = None
        self._vault_available = False
        self._encryption_key = None

        # Try to initialize Vault
        if VAULT_ADDR:
            self._init_vault()
        else:
            # Development fallback: use encryption key
            self._init_encryption()

    def _init_vault(self):
        """Initialize Vault client for credential storage."""
        try:
            import hvac

            vault_token = os.getenv("VAULT_TOKEN")
            if not vault_token:
                logger.warning("VAULT_ADDR set but no VAULT_TOKEN - Vault disabled")
                self._init_encryption()
                return

            self._vault_client = hvac.Client(
                url=VAULT_ADDR,
                token=vault_token,
            )

            if self._vault_client.is_authenticated():
                self._vault_available = True
                logger.info("Mail credentials service: Vault initialized")
            else:
                logger.warning("Vault authentication failed - using encryption fallback")
                self._init_encryption()

        except ImportError:
            logger.warning("hvac not installed - using encryption fallback")
            self._init_encryption()
        except Exception as e:
            logger.warning(f"Vault initialization failed: {e}")
            self._init_encryption()

    def _init_encryption(self):
        """Initialize encryption for development/fallback mode."""
        # Derive key from environment secret - REQUIRED
        secret = os.getenv("MAIL_ENCRYPTION_SECRET")
        if not secret:
            raise RuntimeError("MAIL_ENCRYPTION_SECRET nicht konfiguriert - bitte via Vault oder Umgebungsvariable setzen")
        salt = os.getenv("MAIL_ENCRYPTION_SALT", "breakpilot-mail-salt").encode()

        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt,
            iterations=480000,
        )

        key = base64.urlsafe_b64encode(kdf.derive(secret.encode()))
        self._encryption_key = Fernet(key)
        logger.info("Mail credentials service: Using encrypted storage")

    def _get_vault_path(self, account_id: str) -> str:
        """Generate Vault path for a mail account."""
        return f"breakpilot/mail/accounts/{account_id}"

    async def store_credentials(
        self,
        account_id: str,
        email: str,
        password: str,
        imap_host: str,
        imap_port: int,
        smtp_host: str,
        smtp_port: int,
    ) -> str:
        """
        Store mail credentials securely.

        Returns:
            vault_path: Path or reference to stored credentials
        """
        if self._vault_available:
            return await self._store_in_vault(
                account_id, email, password, imap_host, imap_port, smtp_host, smtp_port
            )
        else:
            return await self._store_encrypted(
                account_id, email, password, imap_host, imap_port, smtp_host, smtp_port
            )

    async def _store_in_vault(
        self,
        account_id: str,
        email: str,
        password: str,
        imap_host: str,
        imap_port: int,
        smtp_host: str,
        smtp_port: int,
    ) -> str:
        """Store credentials in Vault KV v2."""
        path = self._get_vault_path(account_id)

        try:
            self._vault_client.secrets.kv.v2.create_or_update_secret(
                path=path,
                secret={
                    "email": email,
                    "password": password,
                    "imap_host": imap_host,
                    "imap_port": str(imap_port),
                    "smtp_host": smtp_host,
                    "smtp_port": str(smtp_port),
                },
                mount_point="secret",
            )
            logger.info(f"Stored credentials in Vault for account {account_id}")
            return f"vault:{path}"

        except Exception as e:
            logger.error(f"Failed to store credentials in Vault: {e}")
            raise

    async def _store_encrypted(
        self,
        account_id: str,
        email: str,
        password: str,
        imap_host: str,
        imap_port: int,
        smtp_host: str,
        smtp_port: int,
    ) -> str:
        """Store credentials encrypted (development fallback)."""
        import json

        credentials = {
            "email": email,
            "password": password,
            "imap_host": imap_host,
            "imap_port": imap_port,
            "smtp_host": smtp_host,
            "smtp_port": smtp_port,
        }

        # Encrypt the credentials
        encrypted = self._encryption_key.encrypt(json.dumps(credentials).encode())

        # Store in file (development only)
        os.makedirs(MAIL_CREDENTIALS_DIR, exist_ok=True)

        path = f"{MAIL_CREDENTIALS_DIR}/{account_id}.enc"
        with open(path, "wb") as f:
            f.write(encrypted)

        logger.info(f"Stored encrypted credentials for account {account_id}")
        return f"file:{path}"

    async def get_credentials(self, account_id: str, vault_path: str) -> Optional[MailCredentials]:
        """
        Retrieve mail credentials.

        Args:
            account_id: The account ID
            vault_path: The storage path (from store_credentials)

        Returns:
            MailCredentials or None if not found
        """
        if vault_path.startswith("vault:"):
            return await self._get_from_vault(vault_path[6:])
        elif vault_path.startswith("file:"):
            return await self._get_from_file(vault_path[5:])
        else:
            # Legacy path format
            return await self._get_from_vault(vault_path)

    async def _get_from_vault(self, path: str) -> Optional[MailCredentials]:
        """Retrieve credentials from Vault."""
        if not self._vault_available:
            logger.warning("Vault not available for credential retrieval")
            return None

        try:
            response = self._vault_client.secrets.kv.v2.read_secret_version(
                path=path,
                mount_point="secret",
            )

            if response and "data" in response and "data" in response["data"]:
                data = response["data"]["data"]
                return MailCredentials(
                    email=data["email"],
                    password=data["password"],
                    imap_host=data["imap_host"],
                    imap_port=int(data["imap_port"]),
                    smtp_host=data["smtp_host"],
                    smtp_port=int(data["smtp_port"]),
                )

        except Exception as e:
            logger.error(f"Failed to retrieve credentials from Vault: {e}")

        return None

    async def _get_from_file(self, path: str) -> Optional[MailCredentials]:
        """Retrieve credentials from encrypted file (development)."""
        import json

        try:
            with open(path, "rb") as f:
                encrypted = f.read()

            decrypted = self._encryption_key.decrypt(encrypted)
            data = json.loads(decrypted.decode())

            return MailCredentials(
                email=data["email"],
                password=data["password"],
                imap_host=data["imap_host"],
                imap_port=data["imap_port"],
                smtp_host=data["smtp_host"],
                smtp_port=data["smtp_port"],
            )

        except FileNotFoundError:
            logger.warning(f"Credentials file not found: {path}")
        except Exception as e:
            logger.error(f"Failed to decrypt credentials: {e}")

        return None

    async def delete_credentials(self, account_id: str, vault_path: str) -> bool:
        """
        Delete stored credentials.

        Args:
            account_id: The account ID
            vault_path: The storage path

        Returns:
            True if deleted successfully
        """
        if vault_path.startswith("vault:"):
            return await self._delete_from_vault(vault_path[6:])
        elif vault_path.startswith("file:"):
            return await self._delete_from_file(vault_path[5:])
        return False

    async def _delete_from_vault(self, path: str) -> bool:
        """Delete credentials from Vault."""
        if not self._vault_available:
            return False

        try:
            self._vault_client.secrets.kv.v2.delete_metadata_and_all_versions(
                path=path,
                mount_point="secret",
            )
            logger.info(f"Deleted credentials from Vault: {path}")
            return True

        except Exception as e:
            logger.error(f"Failed to delete credentials from Vault: {e}")
            return False

    async def _delete_from_file(self, path: str) -> bool:
        """Delete credentials file."""
        try:
            os.remove(path)
            logger.info(f"Deleted credentials file: {path}")
            return True
        except FileNotFoundError:
            return True  # Already deleted
        except Exception as e:
            logger.error(f"Failed to delete credentials file: {e}")
            return False

    async def update_password(
        self,
        account_id: str,
        vault_path: str,
        new_password: str,
    ) -> bool:
        """
        Update the password for stored credentials.

        Args:
            account_id: The account ID
            vault_path: The storage path
            new_password: The new password

        Returns:
            True if updated successfully
        """
        # Get existing credentials
        creds = await self.get_credentials(account_id, vault_path)
        if not creds:
            return False

        # Store with new password
        try:
            await self.store_credentials(
                account_id=account_id,
                email=creds.email,
                password=new_password,
                imap_host=creds.imap_host,
                imap_port=creds.imap_port,
                smtp_host=creds.smtp_host,
                smtp_port=creds.smtp_port,
            )
            return True
        except Exception as e:
            logger.error(f"Failed to update password: {e}")
            return False


# Global instance
_credentials_service: Optional[MailCredentialsService] = None


def get_credentials_service() -> MailCredentialsService:
    """Get or create the global MailCredentialsService instance."""
    global _credentials_service

    if _credentials_service is None:
        _credentials_service = MailCredentialsService()

    return _credentials_service
