"""Credential Vault — Secure credential generation and management.

Generates, stores, and rotates secrets for all NIKAME infrastructure
components. Only generates credentials for selected components (not blindly
for everything). Separates auto-generated secrets (DB passwords, JWT) from
external API keys (Stripe, OpenAI) which are prompted interactively.
"""

from __future__ import annotations

import hashlib
import secrets
import string
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any

from nikame.utils.logger import console, get_logger

_log = get_logger("credential_vault")


# ─────────────────────────── Data Classes ───────────────────────────


@dataclass
class EnvVarSpec:
    """Specification for a single environment variable."""

    name: str
    value: str
    description: str
    sensitive: bool = False
    auto_generated: bool = False
    source_service: str = ""


@dataclass
class CredentialSet:
    """All credentials for a specific service."""

    service: str
    env_vars: list[EnvVarSpec] = field(default_factory=list)


# ─────────────────────────── Credential Vault ───────────────────────────


class CredentialVault:
    """Component-aware credential generator.

    Usage:
        vault = CredentialVault(project_name="myapp")
        credentials = vault.generate(["postgres", "dragonfly", "jwt"])
        vault.write_env_generated(output_dir)
        vault.write_env_example(output_dir)
    """

    # Services that have auto-generated credentials (never prompted)
    AUTO_GENERATED_SERVICES = {
        "postgres", "dragonfly", "redis", "jwt", "minio",
        "redpanda", "keycloak", "grafana",
    }

    # Services that require external API keys (always prompted)
    EXTERNAL_SERVICES = {
        "stripe", "openai", "anthropic", "sendgrid", "resend",
        "sentry", "aws", "gcp",
    }

    def __init__(self, project_name: str) -> None:
        self.project_name = project_name
        self._credentials: dict[str, CredentialSet] = {}
        self._all_env_vars: dict[str, EnvVarSpec] = {}

    # ──────────────────── Core API ────────────────────

    def generate(self, components: list[str]) -> dict[str, str]:
        """Generate credentials for the given list of components.

        Only generates secrets for components that are actually selected.
        Returns a flat dict of all env var name → value pairs.

        Args:
            components: List of component names (e.g., ["postgres", "dragonfly", "jwt"])

        Returns:
            Dict of all env var name → value pairs.
        """
        _log.debug("Generating credentials for components: %s", components)

        for component in components:
            generator = self._GENERATORS.get(component)
            if generator:
                cred_set = generator(self)
                self._credentials[component] = cred_set
                for env_var in cred_set.env_vars:
                    self._all_env_vars[env_var.name] = env_var

        # Always add common app-level vars
        self._add_app_vars()

        return {name: spec.value for name, spec in self._all_env_vars.items()}

    def prompt_external_keys(
        self,
        components: list[str],
        *,
        interactive: bool = True,
    ) -> dict[str, str]:
        """Prompt user for external API keys that cannot be auto-generated.

        Args:
            components: List of component names.
            interactive: If False, skip prompting and use empty strings.

        Returns:
            Dict of prompted env var name → value pairs.
        """
        prompted: dict[str, str] = {}

        for component in components:
            prompter = self._PROMPTERS.get(component)
            if prompter:
                cred_set = prompter(self, interactive=interactive)
                self._credentials[component] = cred_set
                for env_var in cred_set.env_vars:
                    self._all_env_vars[env_var.name] = env_var
                    prompted[env_var.name] = env_var.value

        return prompted

    def rotate(self, service: str) -> dict[str, str]:
        """Regenerate credentials for a specific service.

        Args:
            service: Service name (e.g., "postgres", "jwt").

        Returns:
            Dict of rotated env var name → new value.
        """
        generator = self._GENERATORS.get(service)
        if not generator:
            raise ValueError(f"No credential generator for service: {service}")

        cred_set = generator(self)
        self._credentials[service] = cred_set

        rotated: dict[str, str] = {}
        for env_var in cred_set.env_vars:
            self._all_env_vars[env_var.name] = env_var
            rotated[env_var.name] = env_var.value

        _log.info("Rotated credentials for service: %s (%d vars)", service, len(rotated))
        return rotated

    def get_all_env_vars(self) -> dict[str, str]:
        """Return all accumulated env vars as a flat dict."""
        return {name: spec.value for name, spec in self._all_env_vars.items()}

    def get_env_descriptions(self) -> dict[str, str]:
        """Return all env var descriptions for .env.example generation."""
        return {name: spec.description for name, spec in self._all_env_vars.items()}

    def get_sensitive_vars(self) -> set[str]:
        """Return set of env var names that contain secrets."""
        return {name for name, spec in self._all_env_vars.items() if spec.sensitive}

    def affected_services(self, service: str) -> list[str]:
        """Return services that need restarting when a service's credentials change.

        Args:
            service: The service whose credentials changed.

        Returns:
            List of Docker Compose service names to restart.
        """
        # Map of credential service → docker compose service names that use those creds
        dependency_map: dict[str, list[str]] = {
            "postgres": [
                "postgres", "postgres-replica", "pgbouncer",
                "pgbouncer-ro", "api", "migrate", "seed",
            ],
            "dragonfly": ["dragonfly", "api"],
            "redis": ["redis", "api"],
            "jwt": ["api"],
            "minio": ["minio", "api"],
            "redpanda": ["redpanda", "api"],
            "keycloak": ["keycloak", "api"],
            "grafana": ["grafana"],
        }
        return dependency_map.get(service, ["api"])

    # ──────────────────── File I/O ────────────────────

    def write_env_generated(self, output_dir: Path) -> None:
        """Write .env.generated with real credential values."""
        lines = [
            "# ━━━ Auto-generated by NIKAME — DO NOT COMMIT ━━━",
            f"# Project: {self.project_name}",
            "# Rotate with: nikame secrets rotate <service>",
            "",
        ]

        current_service = ""
        for name, spec in self._all_env_vars.items():
            if spec.source_service != current_service:
                current_service = spec.source_service
                lines.append(f"\n# ── {current_service.upper() or 'APP'} ──")
            lines.append(f"{name}={spec.value}")

        env_path = output_dir / ".env.generated"
        env_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
        _log.info("Wrote %d env vars to %s", len(self._all_env_vars), env_path)

    def write_env_example(self, output_dir: Path) -> None:
        """Write .env.example with descriptions and CHANGEME placeholders."""
        lines = [
            "# ━━━ NIKAME Environment Variables ━━━",
            "# Copy to .env and fill in values",
            "",
        ]

        current_service = ""
        for name, spec in self._all_env_vars.items():
            if spec.source_service != current_service:
                current_service = spec.source_service
                lines.append(f"\n# ── {current_service.upper() or 'APP'} ──")

            lines.append(f"# {spec.description}")
            if spec.sensitive:
                lines.append(f"{name}=CHANGEME")
            else:
                lines.append(f"{name}={spec.value}")

        env_path = output_dir / ".env.example"
        env_path.write_text("\n".join(lines) + "\n", encoding="utf-8")

    def update_env_file(self, env_path: Path, updates: dict[str, str]) -> None:
        """Update specific variables in an existing .env file.

        Args:
            env_path: Path to the .env file.
            updates: Dict of var name → new value.
        """
        if not env_path.exists():
            # Write all as new
            lines = [f"{k}={v}" for k, v in updates.items()]
            env_path.write_text("\n".join(lines) + "\n", encoding="utf-8")
            return

        content = env_path.read_text(encoding="utf-8")
        new_lines = []
        updated_keys: set[str] = set()

        for line in content.splitlines():
            stripped = line.strip()
            if stripped and not stripped.startswith("#") and "=" in stripped:
                key = stripped.split("=", 1)[0].strip()
                if key in updates:
                    new_lines.append(f"{key}={updates[key]}")
                    updated_keys.add(key)
                    continue
            new_lines.append(line)

        # Append any new vars not already in file
        for key, value in updates.items():
            if key not in updated_keys:
                new_lines.append(f"{key}={value}")

        env_path.write_text("\n".join(new_lines) + "\n", encoding="utf-8")

    # ──────────────────── Password Utilities ────────────────────

    @staticmethod
    def _generate_password(length: int = 32) -> str:
        """Generate a URL-safe random password."""
        return secrets.token_urlsafe(length)

    @staticmethod
    def _generate_hex_secret(length: int = 64) -> str:
        """Generate a hex-encoded secret."""
        return secrets.token_hex(length // 2)

    @staticmethod
    def _pgbouncer_md5(username: str, password: str) -> str:
        """Generate MD5 auth string for pgbouncer userlist.txt."""
        md5_hash = hashlib.md5(
            (password + username).encode("utf-8")
        ).hexdigest()
        return f"md5{md5_hash}"

    # ──────────────────── Service Generators ────────────────────

    def _generate_postgres(self) -> CredentialSet:
        """Generate PostgreSQL credentials."""
        user = "nikame_app"
        password = self._generate_password()
        db = self.project_name.replace("-", "_")
        replica_password = self._generate_password()

        host = "pgbouncer"  # Default to pgbouncer
        port = "5432"
        db_url = f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{db}"
        test_db_url = f"postgresql+asyncpg://{user}:{password}@localhost:{port}/{db}_test"

        return CredentialSet(
            service="postgres",
            env_vars=[
                EnvVarSpec("POSTGRES_USER", user, "PostgreSQL application username", source_service="postgres"),
                EnvVarSpec("POSTGRES_PASSWORD", password, "PostgreSQL application password (auto-generated)", sensitive=True, auto_generated=True, source_service="postgres"),
                EnvVarSpec("POSTGRES_DB", db, "PostgreSQL database name", source_service="postgres"),
                EnvVarSpec("POSTGRES_REPLICA_USER", "nikame_replica", "PostgreSQL replication user", source_service="postgres"),
                EnvVarSpec("POSTGRES_REPLICA_PASSWORD", replica_password, "PostgreSQL replication password (auto-generated)", sensitive=True, auto_generated=True, source_service="postgres"),
                EnvVarSpec("DATABASE_URL", db_url, "Primary database connection string", sensitive=True, auto_generated=True, source_service="postgres"),
                EnvVarSpec("TEST_DATABASE_URL", test_db_url, "Test database connection string", sensitive=True, auto_generated=True, source_service="postgres"),
            ],
        )

    def _generate_dragonfly(self) -> CredentialSet:
        """Generate Dragonfly/Redis credentials."""
        password = self._generate_password()
        redis_url = f"redis://:{password}@dragonfly:6379/0"
        test_redis_url = f"redis://:{password}@localhost:6379/1"

        return CredentialSet(
            service="dragonfly",
            env_vars=[
                EnvVarSpec("REDIS_PASSWORD", password, "Dragonfly/Redis password (auto-generated)", sensitive=True, auto_generated=True, source_service="dragonfly"),
                EnvVarSpec("REDIS_URL", redis_url, "Redis connection URL (primary)", sensitive=True, auto_generated=True, source_service="dragonfly"),
                EnvVarSpec("CACHE_URL", redis_url, "Cache connection URL (alias for REDIS_URL)", sensitive=True, auto_generated=True, source_service="dragonfly"),
                EnvVarSpec("TEST_REDIS_URL", test_redis_url, "Redis connection URL (test)", sensitive=True, auto_generated=True, source_service="dragonfly"),
            ],
        )

    def _generate_redis(self) -> CredentialSet:
        """Generate Redis credentials (when using standalone Redis, not Dragonfly)."""
        password = self._generate_password()
        redis_url = f"redis://:{password}@redis:6379/0"
        test_redis_url = f"redis://:{password}@localhost:6379/1"

        return CredentialSet(
            service="redis",
            env_vars=[
                EnvVarSpec("REDIS_PASSWORD", password, "Redis password (auto-generated)", sensitive=True, auto_generated=True, source_service="redis"),
                EnvVarSpec("REDIS_URL", redis_url, "Redis connection URL", sensitive=True, auto_generated=True, source_service="redis"),
                EnvVarSpec("CACHE_URL", redis_url, "Cache connection URL", sensitive=True, auto_generated=True, source_service="redis"),
                EnvVarSpec("TEST_REDIS_URL", test_redis_url, "Redis test URL", sensitive=True, auto_generated=True, source_service="redis"),
            ],
        )

    def _generate_jwt(self) -> CredentialSet:
        """Generate JWT signing credentials."""
        secret = self._generate_hex_secret(64)

        return CredentialSet(
            service="jwt",
            env_vars=[
                EnvVarSpec("JWT_SECRET_KEY", secret, "JWT signing secret (auto-generated)", sensitive=True, auto_generated=True, source_service="jwt"),
                EnvVarSpec("JWT_ALGORITHM", "HS256", "JWT algorithm", source_service="jwt"),
                EnvVarSpec("JWT_EXPIRE_MINUTES", "1440", "JWT token expiry (minutes)", source_service="jwt"),
            ],
        )

    def _generate_minio(self) -> CredentialSet:
        """Generate MinIO credentials."""
        root_password = self._generate_password()

        return CredentialSet(
            service="minio",
            env_vars=[
                EnvVarSpec("MINIO_ROOT_USER", "minioadmin", "MinIO root username", source_service="minio"),
                EnvVarSpec("MINIO_ROOT_PASSWORD", root_password, "MinIO root password (auto-generated)", sensitive=True, auto_generated=True, source_service="minio"),
                EnvVarSpec("MINIO_ENDPOINT", "minio:9000", "MinIO endpoint", source_service="minio"),
            ],
        )

    def _generate_redpanda(self) -> CredentialSet:
        """Generate Redpanda/Kafka credentials."""
        return CredentialSet(
            service="redpanda",
            env_vars=[
                EnvVarSpec("KAFKA_BOOTSTRAP_SERVERS", "redpanda:9092", "Kafka bootstrap servers", source_service="redpanda"),
            ],
        )

    def _generate_keycloak(self) -> CredentialSet:
        """Generate Keycloak credentials."""
        admin_password = self._generate_password()

        return CredentialSet(
            service="keycloak",
            env_vars=[
                EnvVarSpec("KEYCLOAK_ADMIN", "admin", "Keycloak admin username", source_service="keycloak"),
                EnvVarSpec("KEYCLOAK_ADMIN_PASSWORD", admin_password, "Keycloak admin password (auto-generated)", sensitive=True, auto_generated=True, source_service="keycloak"),
            ],
        )

    def _generate_grafana(self) -> CredentialSet:
        """Generate Grafana credentials."""
        admin_password = self._generate_password()

        return CredentialSet(
            service="grafana",
            env_vars=[
                EnvVarSpec("GRAFANA_ADMIN_PASSWORD", admin_password, "Grafana admin password (auto-generated)", sensitive=True, auto_generated=True, source_service="grafana"),
            ],
        )

    def _generate_qdrant(self) -> CredentialSet:
        """Generate Qdrant configuration."""
        collection = f"{self.project_name.replace('-', '_')}_embeddings"

        return CredentialSet(
            service="qdrant",
            env_vars=[
                EnvVarSpec("QDRANT_URL", "http://qdrant:6333", "Qdrant vector DB URL", source_service="qdrant"),
                EnvVarSpec("QDRANT_COLLECTION", collection, "Default Qdrant collection name", source_service="qdrant"),
            ],
        )

    def _generate_ollama(self) -> CredentialSet:
        """Generate Ollama configuration."""
        return CredentialSet(
            service="ollama",
            env_vars=[
                EnvVarSpec("OLLAMA_HOST", f"http://ollama-{self.project_name}:11434", "Ollama API endpoint", source_service="ollama"),
            ],
        )

    # ──────────────────── External Key Prompters ────────────────────

    def _prompt_stripe(self, *, interactive: bool = True) -> CredentialSet:
        """Prompt for Stripe API keys."""
        sk = ""
        whsec = ""

        if interactive:
            console.print("\n[bold]⚡ Stripe Integration[/bold]")
            console.print("  Get keys from: [link]https://dashboard.stripe.com/apikeys[/link]")
            try:
                import questionary
                sk = questionary.text(
                    "Stripe Secret Key (sk_test_...):",
                    default="",
                ).ask() or ""
                whsec = questionary.text(
                    "Stripe Webhook Secret (whsec_...):",
                    default="",
                ).ask() or ""
            except (ImportError, KeyboardInterrupt):
                pass

            if not sk:
                console.print("  [dim]⏭ Skipped — billing endpoints will return 503[/dim]")

        return CredentialSet(
            service="stripe",
            env_vars=[
                EnvVarSpec("STRIPE_SECRET_KEY", sk, "Stripe API secret key", sensitive=True, source_service="stripe"),
                EnvVarSpec("STRIPE_WEBHOOK_SECRET", whsec, "Stripe webhook signing secret", sensitive=True, source_service="stripe"),
            ],
        )

    def _prompt_openai(self, *, interactive: bool = True) -> CredentialSet:
        """Prompt for OpenAI API key."""
        key = ""

        if interactive:
            console.print("\n[bold]🤖 OpenAI Integration[/bold]")
            console.print("  Get key from: [link]https://platform.openai.com/api-keys[/link]")
            try:
                import questionary
                key = questionary.text(
                    "OpenAI API Key (sk-...):",
                    default="",
                ).ask() or ""
            except (ImportError, KeyboardInterrupt):
                pass

            if not key:
                console.print("  [dim]⏭ Skipped — LLM calls will fail until configured[/dim]")

        return CredentialSet(
            service="openai",
            env_vars=[
                EnvVarSpec("OPENAI_API_KEY", key, "OpenAI API key", sensitive=True, source_service="openai"),
            ],
        )

    # ──────────────────── App-level Variables ────────────────────

    def _add_app_vars(self) -> None:
        """Add common application-level env vars."""
        app_secret = self._generate_password()

        app_vars = [
            EnvVarSpec("APP_NAME", self.project_name, "Application name", source_service="app"),
            EnvVarSpec("APP_ENV", "local", "Application environment", source_service="app"),
            EnvVarSpec("SECRET_KEY", app_secret, "Application-wide signing secret (auto-generated)", sensitive=True, auto_generated=True, source_service="app"),
            EnvVarSpec("CORS_ORIGINS", "http://localhost:3000,http://localhost:8000", "CORS allowed origins (comma-separated)", source_service="app"),
            EnvVarSpec("LOG_LEVEL", "INFO", "Application log level", source_service="app"),
            EnvVarSpec("ENVIRONMENT", "local", "Deployment environment", source_service="app"),
        ]

        for var in app_vars:
            if var.name not in self._all_env_vars:
                self._all_env_vars[var.name] = var

    # ──────────────────── Registry Maps ────────────────────

    _GENERATORS: dict[str, Any] = {}
    _PROMPTERS: dict[str, Any] = {}


# Register generators after class definition (avoids forward-ref issues)
CredentialVault._GENERATORS = {
    "postgres": CredentialVault._generate_postgres,
    "dragonfly": CredentialVault._generate_dragonfly,
    "redis": CredentialVault._generate_redis,
    "jwt": CredentialVault._generate_jwt,
    "minio": CredentialVault._generate_minio,
    "redpanda": CredentialVault._generate_redpanda,
    "keycloak": CredentialVault._generate_keycloak,
    "grafana": CredentialVault._generate_grafana,
    "qdrant": CredentialVault._generate_qdrant,
    "ollama": CredentialVault._generate_ollama,
}

CredentialVault._PROMPTERS = {
    "stripe": CredentialVault._prompt_stripe,
    "openai": CredentialVault._prompt_openai,
}
