"""Credentials management for Promethian tools.

Handles checking for required API keys and prompting users to set them up.
Tools can declare what credentials they need, and the system will:
1. Check if they exist in environment or .env file
2. Prompt the user with setup instructions if missing
"""

from __future__ import annotations

import json
import os
from dataclasses import dataclass, field
from pathlib import Path
from typing import Callable


@dataclass
class CredentialRequirement:
    """A credential that a tool requires."""

    # The environment variable name
    env_var: str

    # Human-readable name (e.g., "OpenAI API Key")
    display_name: str

    # Description of what this credential is for
    description: str

    # URL where the user can get this credential
    signup_url: str | None = None

    # Whether this credential is required (vs optional)
    required: bool = True

    # Validation function (returns True if valid)
    validator: Callable[[str], bool] | None = None

    # Example format for documentation
    example_format: str | None = None


@dataclass
class CredentialStatus:
    """Status of a credential check."""

    requirement: CredentialRequirement
    is_set: bool
    is_valid: bool | None = None  # None if no validator
    value: str | None = None  # Masked or None if not set


@dataclass
class MissingCredentialsError(Exception):
    """Raised when required credentials are missing."""

    missing: list[CredentialRequirement]
    setup_instructions: str

    def __init__(self, missing: list[CredentialRequirement]):
        self.missing = missing
        self.setup_instructions = self._generate_instructions()
        super().__init__(self.setup_instructions)

    def _generate_instructions(self) -> str:
        """Generate setup instructions for missing credentials."""
        lines = [
            "Missing required credentials. Please set up the following:\n",
        ]

        for req in self.missing:
            lines.append(f"## {req.display_name}")
            lines.append(f"   Environment variable: {req.env_var}")
            lines.append(f"   Description: {req.description}")

            if req.signup_url:
                lines.append(f"   Get it here: {req.signup_url}")

            if req.example_format:
                lines.append(f"   Format: {req.example_format}")

            lines.append("")

        lines.append("To set up credentials, either:")
        lines.append("")
        lines.append("1. Add to your shell profile (~/.zshrc or ~/.bashrc):")
        for req in self.missing:
            lines.append(f'   export {req.env_var}="your-key-here"')

        lines.append("")
        lines.append("2. Or create a .env file in your project:")
        for req in self.missing:
            lines.append(f'   {req.env_var}=your-key-here')

        lines.append("")
        lines.append("Then restart your terminal or run: source ~/.zshrc")

        return "\n".join(lines)


# Common credential requirements
OPENAI_API_KEY = CredentialRequirement(
    env_var="OPENAI_API_KEY",
    display_name="OpenAI API Key",
    description="Required for embeddings and GPT models",
    signup_url="https://platform.openai.com/api-keys",
    example_format="sk-...",
    validator=lambda x: x.startswith("sk-") and len(x) > 20,
)

ANTHROPIC_API_KEY = CredentialRequirement(
    env_var="ANTHROPIC_API_KEY",
    display_name="Anthropic API Key",
    description="Required for Claude models",
    signup_url="https://console.anthropic.com/settings/keys",
    example_format="sk-ant-...",
    validator=lambda x: x.startswith("sk-ant-") and len(x) > 20,
)

PROMETHIAN_API_KEY = CredentialRequirement(
    env_var="PROMETHIAN_API_KEY",
    display_name="Promethian API Key",
    description="Required to access Promethian's centralized knowledge base",
    signup_url="https://promethian.io/signup",
    example_format="pm_...",
    validator=lambda x: x.startswith("pm_") and len(x) > 10,
)

STRIPE_API_KEY = CredentialRequirement(
    env_var="STRIPE_SECRET_KEY",
    display_name="Stripe Secret Key",
    description="Required for payment processing",
    signup_url="https://dashboard.stripe.com/apikeys",
    example_format="sk_live_... or sk_test_...",
    validator=lambda x: x.startswith("sk_") and len(x) > 20,
)

GITHUB_TOKEN = CredentialRequirement(
    env_var="GITHUB_TOKEN",
    display_name="GitHub Personal Access Token",
    description="Required for GitHub API access",
    signup_url="https://github.com/settings/tokens",
    example_format="ghp_...",
    validator=lambda x: x.startswith("ghp_") or x.startswith("github_pat_"),
)

LINKEDIN_EMAIL = CredentialRequirement(
    env_var="LINKEDIN_EMAIL",
    display_name="LinkedIn Email",
    description="Email address for LinkedIn login (for browser automation)",
    example_format="your.email@example.com",
    validator=lambda x: "@" in x and "." in x,
)

LINKEDIN_PASSWORD = CredentialRequirement(
    env_var="LINKEDIN_PASSWORD",
    display_name="LinkedIn Password",
    description="Password for LinkedIn login (for browser automation)",
    # No validator for passwords - any non-empty string is valid
)


class CredentialManager:
    """
    Manages credentials for tools.

    Credentials are loaded from (in order of priority):
    1. Environment variables
    2. .env file in working directory
    3. ~/.promethian/credentials.json (local secure storage)

    Usage:
        manager = CredentialManager()
        manager.require(OPENAI_API_KEY)
        manager.check()  # Raises MissingCredentialsError if missing
        key = manager.get("OPENAI_API_KEY")
    """

    def __init__(self, env_file: Path | str | None = None):
        """
        Initialize the credential manager.

        Args:
            env_file: Path to .env file (default: .env in current directory)
        """
        self.requirements: dict[str, CredentialRequirement] = {}
        self.env_file = Path(env_file) if env_file else Path.cwd() / ".env"
        self.credentials_file = Path.home() / ".promethian" / "credentials.json"

        # Load credentials from all sources
        self._load_local_credentials()  # Load from ~/.promethian/credentials.json first
        self._load_env_file()  # Then .env (overrides local)

    def _load_local_credentials(self) -> None:
        """Load credentials from ~/.promethian/credentials.json."""
        if not self.credentials_file.exists():
            return

        try:
            with open(self.credentials_file) as f:
                creds = json.load(f)

            # Set as environment variables (lowest priority - can be overridden)
            for key, value in creds.items():
                if key and value and key not in os.environ:
                    os.environ[key] = value
        except Exception:
            pass  # Ignore parsing errors

    def _load_env_file(self) -> None:
        """Load environment variables from .env file."""
        if not self.env_file.exists():
            return

        try:
            with open(self.env_file) as f:
                for line in f:
                    line = line.strip()
                    if not line or line.startswith("#"):
                        continue
                    if "=" in line:
                        key, value = line.split("=", 1)
                        key = key.strip()
                        value = value.strip().strip("'\"")
                        if key and value and key not in os.environ:
                            os.environ[key] = value
        except Exception:
            pass  # Ignore .env parsing errors

    def save_credential(self, env_var: str, value: str) -> bool:
        """
        Save a credential to local storage (~/.promethian/credentials.json).

        Args:
            env_var: The environment variable name
            value: The credential value

        Returns:
            True if saved successfully, False otherwise
        """
        try:
            # Ensure directory exists
            self.credentials_file.parent.mkdir(parents=True, exist_ok=True)

            # Load existing credentials
            creds = {}
            if self.credentials_file.exists():
                with open(self.credentials_file) as f:
                    creds = json.load(f)

            # Update with new credential
            creds[env_var] = value

            # Save back
            with open(self.credentials_file, "w") as f:
                json.dump(creds, f, indent=2)

            # Also set in environment for current session
            os.environ[env_var] = value

            # Set restrictive permissions (owner read/write only)
            self.credentials_file.chmod(0o600)

            return True
        except Exception:
            return False

    def delete_credential(self, env_var: str) -> bool:
        """
        Delete a credential from local storage.

        Args:
            env_var: The environment variable name

        Returns:
            True if deleted successfully, False otherwise
        """
        try:
            if not self.credentials_file.exists():
                return True

            with open(self.credentials_file) as f:
                creds = json.load(f)

            if env_var in creds:
                del creds[env_var]

                with open(self.credentials_file, "w") as f:
                    json.dump(creds, f, indent=2)

            # Remove from environment too
            if env_var in os.environ:
                del os.environ[env_var]

            return True
        except Exception:
            return False

    def list_stored_credentials(self) -> dict[str, str]:
        """
        List all locally stored credentials (masked).

        Returns:
            Dict of env_var -> masked_value
        """
        result = {}
        if not self.credentials_file.exists():
            return result

        try:
            with open(self.credentials_file) as f:
                creds = json.load(f)

            for key, value in creds.items():
                if len(value) > 8:
                    result[key] = f"{value[:4]}...{value[-4:]}"
                else:
                    result[key] = "***"
        except Exception:
            pass

        return result

    def get_available_credential_keys(self) -> list[str]:
        """
        Get list of credential keys that are set (from any source).

        This allows checking what credentials exist without reading values.

        Returns:
            List of environment variable names that have values set
        """
        # Get keys from local storage
        local_keys = set()
        if self.credentials_file.exists():
            try:
                with open(self.credentials_file) as f:
                    creds = json.load(f)
                local_keys = set(creds.keys())
            except Exception:
                pass

        # Get keys from environment (checking common credential patterns)
        env_keys = set()
        credential_patterns = [
            "_API_KEY", "_SECRET_KEY", "_TOKEN", "_PASSWORD",
            "_EMAIL", "_USERNAME", "_CREDENTIAL", "_AUTH",
        ]
        for key in os.environ:
            if any(pattern in key for pattern in credential_patterns):
                env_keys.add(key)

        return sorted(local_keys | env_keys)

    def get_known_credentials_status(self) -> dict[str, bool]:
        """
        Get status of all known/common credentials.

        Returns a dict of credential name -> is_set for common credentials
        like OPENAI_API_KEY, ANTHROPIC_API_KEY, etc.

        Returns:
            Dict of credential display_name -> is_set boolean
        """
        from promethian.utils.credentials import (
            OPENAI_API_KEY, ANTHROPIC_API_KEY, PROMETHIAN_API_KEY,
            STRIPE_API_KEY, GITHUB_TOKEN, LINKEDIN_EMAIL, LINKEDIN_PASSWORD,
        )

        known = [
            OPENAI_API_KEY,
            ANTHROPIC_API_KEY,
            PROMETHIAN_API_KEY,
            STRIPE_API_KEY,
            GITHUB_TOKEN,
            LINKEDIN_EMAIL,
            LINKEDIN_PASSWORD,
        ]

        return {
            req.display_name: bool(os.environ.get(req.env_var))
            for req in known
        }

    def is_credential_set(self, env_var: str) -> bool:
        """
        Check if a specific credential is set (without reading the value).

        Args:
            env_var: The environment variable name to check

        Returns:
            True if the credential has a value, False otherwise
        """
        return bool(os.environ.get(env_var))

    def require(self, *requirements: CredentialRequirement) -> "CredentialManager":
        """
        Add credential requirements.

        Args:
            requirements: One or more CredentialRequirement objects

        Returns:
            Self for chaining
        """
        for req in requirements:
            self.requirements[req.env_var] = req
        return self

    def check_status(self, requirement: CredentialRequirement) -> CredentialStatus:
        """
        Check the status of a single credential.

        Args:
            requirement: The credential requirement to check

        Returns:
            CredentialStatus with current state
        """
        value = os.environ.get(requirement.env_var)
        is_set = bool(value)

        is_valid = None
        if is_set and requirement.validator:
            is_valid = requirement.validator(value)

        # Mask the value for display
        masked_value = None
        if value:
            if len(value) > 8:
                masked_value = f"{value[:4]}...{value[-4:]}"
            else:
                masked_value = "***"

        return CredentialStatus(
            requirement=requirement,
            is_set=is_set,
            is_valid=is_valid,
            value=masked_value,
        )

    def check(self, raise_on_missing: bool = True) -> list[CredentialStatus]:
        """
        Check all required credentials.

        Args:
            raise_on_missing: Whether to raise MissingCredentialsError if missing

        Returns:
            List of CredentialStatus objects

        Raises:
            MissingCredentialsError: If required credentials are missing
        """
        statuses = []
        missing = []

        for req in self.requirements.values():
            status = self.check_status(req)
            statuses.append(status)

            if req.required and not status.is_set:
                missing.append(req)
            elif req.required and status.is_valid is False:
                missing.append(req)

        if missing and raise_on_missing:
            raise MissingCredentialsError(missing)

        return statuses

    def get(self, env_var: str, default: str | None = None) -> str | None:
        """
        Get a credential value.

        Args:
            env_var: The environment variable name
            default: Default value if not set

        Returns:
            The credential value or default
        """
        return os.environ.get(env_var, default)

    def get_required(self, env_var: str) -> str:
        """
        Get a required credential value.

        Args:
            env_var: The environment variable name

        Returns:
            The credential value

        Raises:
            MissingCredentialsError: If the credential is not set
        """
        value = os.environ.get(env_var)
        if not value:
            req = self.requirements.get(env_var)
            if req:
                raise MissingCredentialsError([req])
            else:
                raise MissingCredentialsError([
                    CredentialRequirement(
                        env_var=env_var,
                        display_name=env_var,
                        description="Required credential",
                    )
                ])
        return value

    def suggest_env_file_content(self) -> str:
        """
        Generate suggested .env file content for all requirements.

        Returns:
            String content for a .env file
        """
        lines = ["# Promethian credentials", "# Copy this to .env and fill in your keys", ""]

        for req in self.requirements.values():
            lines.append(f"# {req.display_name}: {req.description}")
            if req.signup_url:
                lines.append(f"# Get it at: {req.signup_url}")
            example = req.example_format or "your-key-here"
            lines.append(f"{req.env_var}={example}")
            lines.append("")

        return "\n".join(lines)


# Global credential manager
_manager: CredentialManager | None = None


def get_credential_manager() -> CredentialManager:
    """Get the global credential manager."""
    global _manager
    if _manager is None:
        _manager = CredentialManager()
    return _manager


def require_credentials(*requirements: CredentialRequirement) -> CredentialManager:
    """
    Require credentials and check they're set.

    Args:
        requirements: Credential requirements to check

    Returns:
        CredentialManager for further operations

    Raises:
        MissingCredentialsError: If required credentials are missing
    """
    manager = get_credential_manager()
    manager.require(*requirements)
    manager.check()
    return manager
