"""Credential storage backends."""

from abc import ABC, abstractmethod
from pathlib import Path
from typing import Optional
import os
import platform
import base64

from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
from datetime import datetime, timezone, timedelta

from .models import Credentials
from ..models.errors import BootstrapError, AuthenticationError


class CredentialStorage(ABC):
    """Abstract base for credential storage backends."""

    @abstractmethod
    def store(self, credentials: Credentials) -> None:
        """Store credentials securely."""
        pass

    @abstractmethod
    def load(self) -> Optional[Credentials]:
        """Load stored credentials. Returns None if not found."""
        pass

    @abstractmethod
    def delete(self) -> None:
        """Delete stored credentials."""
        pass

    @abstractmethod
    def exists(self) -> bool:
        """Check if credentials exist."""
        pass


class FileCredentialStorage(CredentialStorage):
    """Encrypted file-based credential storage.

    Default storage location: ~/.synap/instances/{instance_id}/credentials.enc
    Encryption: Fernet (AES-128-CBC) with key derived from machine ID + instance_id
    """

    def __init__(self, instance_id: str, storage_path: Optional[str] = None):
        self.instance_id = instance_id

        if storage_path:
            self.base_path = Path(storage_path)
        else:
            self.base_path = Path.home() / ".synap"

        self.credentials_dir = self.base_path / "instances" / instance_id
        self.credentials_file = self.credentials_dir / "credentials.enc"

        # Derive encryption key
        self._fernet = self._create_fernet()

    def _get_machine_id(self) -> str:
        """Get a stable machine identifier for key derivation."""
        system = platform.system()

        try:
            if system == "Linux":
                # Try machine-id first, fall back to product_uuid
                for path in ["/etc/machine-id", "/sys/class/dmi/id/product_uuid"]:
                    if os.path.exists(path):
                        with open(path, "r") as f:
                            return f.read().strip()
            elif system == "Darwin":  # macOS
                import subprocess
                result = subprocess.run(
                    ["ioreg", "-rd1", "-c", "IOPlatformExpertDevice"],
                    capture_output=True, text=True
                )
                for line in result.stdout.split("\n"):
                    if "IOPlatformUUID" in line:
                        return line.split('"')[-2]
            elif system == "Windows":
                import subprocess
                result = subprocess.run(
                    ["wmic", "csproduct", "get", "UUID"],
                    capture_output=True, text=True
                )
                lines = result.stdout.strip().split("\n")
                if len(lines) > 1:
                    return lines[1].strip()
        except Exception:
            pass

        # Fallback: use hostname + username (less stable but works)
        import getpass
        return f"{platform.node()}:{getpass.getuser()}"

    def _create_fernet(self) -> Fernet:
        """Create Fernet instance with derived key."""
        machine_id = self._get_machine_id()
        salt = f"synap:{self.instance_id}".encode()

        kdf = PBKDF2HMAC(
            algorithm=hashes.SHA256(),
            length=32,
            salt=salt,
            iterations=480000,  # OWASP recommended minimum
        )

        key = base64.urlsafe_b64encode(kdf.derive(machine_id.encode()))
        return Fernet(key)

    def store(self, credentials: Credentials) -> None:
        """Store credentials encrypted to disk."""
        self.credentials_dir.mkdir(parents=True, exist_ok=True)

        # Set restrictive permissions on directory
        self.credentials_dir.chmod(0o700)

        # Encrypt and write
        plaintext = credentials.model_dump_json().encode()
        encrypted = self._fernet.encrypt(plaintext)

        self.credentials_file.write_bytes(encrypted)
        self.credentials_file.chmod(0o600)  # Owner read/write only

    def load(self) -> Optional[Credentials]:
        """Load and decrypt credentials."""
        if not self.credentials_file.exists():
            return None

        try:
            encrypted = self.credentials_file.read_bytes()
            plaintext = self._fernet.decrypt(encrypted)
            return Credentials.model_validate_json(plaintext)
        except Exception as e:
            raise AuthenticationError(f"Failed to decrypt credentials: {e}")

    def delete(self) -> None:
        """Securely delete credentials."""
        if self.credentials_file.exists():
            # Overwrite with random data before unlinking
            size = self.credentials_file.stat().st_size
            self.credentials_file.write_bytes(os.urandom(size))
            self.credentials_file.unlink()

    def exists(self) -> bool:
        """Check if credentials file exists."""
        return self.credentials_file.exists()


class EnvironmentCredentialStorage(CredentialStorage):
    """Read credentials from environment variables.

    Expected variables:
    - SYNAP_API_KEY         cert fingerprint used as Bearer token
    - SYNAP_MTLS_CERT_PATH  path to PEM certificate file
    - SYNAP_MTLS_KEY_PATH   path to PEM private key file
    - SYNAP_CLIENT_ID
    - SYNAP_INSTANCE_ID
    """

    def __init__(self, instance_id: str):
        self.instance_id = instance_id

    def store(self, credentials: Credentials) -> None:
        """Environment storage is read-only."""
        raise NotImplementedError("Cannot store credentials in environment mode")

    def load(self) -> Optional[Credentials]:
        """Load credentials from environment variables."""
        try:
            api_key = os.environ["SYNAP_API_KEY"]
            client_id = os.environ["SYNAP_CLIENT_ID"]

            # Load mTLS cert and key from paths
            cert_path = os.environ.get("SYNAP_MTLS_CERT_PATH", "")
            key_path = os.environ.get("SYNAP_MTLS_KEY_PATH", "")

            mtls_cert = ""
            mtls_key = ""
            if cert_path and key_path:
                with open(cert_path, "r") as f:
                    mtls_cert = f.read()
                with open(key_path, "r") as f:
                    mtls_key = f.read()

            # Environment mode has no expiry metadata — use far future so
            # _is_expired() never triggers (env creds are not rotated at runtime).
            far_future = datetime.now(timezone.utc) + timedelta(days=365)
            now = datetime.now(timezone.utc)

            return Credentials(
                api_key=api_key,
                api_key_expires_at=far_future,
                mtls_cert=mtls_cert,
                mtls_private_key=mtls_key,
                mtls_expires_at=far_future,
                instance_id=self.instance_id,
                client_id=client_id,
                issued_at=now,
            )
        except KeyError as e:
            raise AuthenticationError(f"Missing environment variable: {e}")

    def delete(self) -> None:
        """Environment storage is read-only."""
        pass

    def exists(self) -> bool:
        """Check if required environment variables exist."""
        return "SYNAP_API_KEY" in os.environ and "SYNAP_CLIENT_ID" in os.environ
