# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""
Credential management for self-hosted services.

Generates, stores, and retrieves unique per-service credentials.
Secrets are stored in ~/.familiar/services/secrets.json (chmod 600).
Never hardcoded, never stored in agent memory or RAG.

Security references:
- OWASP ASI06 (Memory Poisoning): credentials kept out of LLM context
- NIST SP 800-190: secrets management for containerized apps
"""

import json
import logging
import os
import secrets
from pathlib import Path
from typing import Dict, Optional

logger = logging.getLogger(__name__)

# Default location for the secrets store
_SECRETS_DIR = Path.home() / ".familiar" / "services"
_SECRETS_FILE = _SECRETS_DIR / "secrets.json"


def _ensure_secrets_file() -> Path:
    """Create the secrets file with restrictive permissions if it doesn't exist."""
    _SECRETS_DIR.mkdir(parents=True, exist_ok=True)
    if not _SECRETS_FILE.exists():
        _SECRETS_FILE.write_text("{}")
    # Always enforce permissions — chmod 600 (owner read/write only)
    try:
        os.chmod(_SECRETS_FILE, 0o600)
    except OSError:
        # Windows doesn't support chmod the same way
        pass
    return _SECRETS_FILE


def _encrypt(value: str) -> str:
    """Encrypt a credential value if cryptography is available."""
    try:
        from familiar.core.credential_store import encrypt_password

        return encrypt_password(value)
    except ImportError:
        return value


def _decrypt(value: str) -> str:
    """Decrypt a credential value, passing through plaintext transparently."""
    try:
        from familiar.core.credential_store import decrypt_password

        return decrypt_password(value)
    except ImportError:
        return value


def _load_all() -> Dict[str, Dict[str, str]]:
    """Load all stored credentials (decrypted transparently)."""
    path = _ensure_secrets_file()
    try:
        raw = json.loads(path.read_text(encoding="utf-8"))
        # Decrypt values transparently
        return {svc: {k: _decrypt(v) for k, v in creds.items()} for svc, creds in raw.items()}
    except (json.JSONDecodeError, OSError):
        return {}


def _save_all(data: Dict[str, Dict[str, str]]) -> None:
    """Save all credentials to disk (encrypted at rest)."""
    path = _ensure_secrets_file()
    # Encrypt values before writing
    encrypted = {svc: {k: _encrypt(v) for k, v in creds.items()} for svc, creds in data.items()}
    path.write_text(json.dumps(encrypted, indent=2), encoding="utf-8")
    try:
        os.chmod(path, 0o600)
    except OSError:
        pass


def generate_credential() -> str:
    """Generate a single secure credential value."""
    return secrets.token_urlsafe(24)


def get_service_credentials(service_key: str, credential_keys: list) -> Dict[str, str]:
    """Get or generate credentials for a service.

    Idempotent: if credentials already exist for this service, they are
    returned as-is. Missing keys are generated and saved.

    Args:
        service_key: The service identifier (e.g. "nextcloud", "pihole")
        credential_keys: List of env var names that need generated values

    Returns:
        Dict mapping env var names to their credential values
    """
    if not credential_keys:
        return {}

    all_creds = _load_all()
    svc_creds = all_creds.get(service_key, {})
    changed = False

    for key in credential_keys:
        if key not in svc_creds:
            svc_creds[key] = generate_credential()
            changed = True
            logger.info("Generated credential %s for %s", key, service_key)

    if changed:
        all_creds[service_key] = svc_creds
        _save_all(all_creds)

    return {k: svc_creds[k] for k in credential_keys if k in svc_creds}


def load_credentials(service_key: str) -> Dict[str, str]:
    """Load stored credentials for a service (empty dict if none)."""
    return _load_all().get(service_key, {})


def delete_credentials(service_key: str) -> bool:
    """Remove stored credentials for a service. Returns True if any were removed."""
    all_creds = _load_all()
    if service_key in all_creds:
        del all_creds[service_key]
        _save_all(all_creds)
        return True
    return False


def get_credential_summary(service_key: str) -> Optional[str]:
    """Get a one-time display summary of credentials for a service.

    Returns a formatted string showing the generated credentials,
    intended for displaying to the user after first provisioning.
    Returns None if no credentials are stored.
    """
    creds = load_credentials(service_key)
    if not creds:
        return None

    lines = [f"Credentials for {service_key}:"]
    for key, value in creds.items():
        # Mask the middle of the value for display
        if len(value) > 8:
            display = value[:4] + "..." + value[-4:]
        else:
            display = value
        lines.append(f"  {key} = {display}")
    lines.append("(stored in ~/.familiar/services/secrets.json)")
    return "\n".join(lines)
