# cyborgdb_service/core/config.py
import os
import secrets
from typing import ClassVar, Optional
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import model_validator
from cyborgdb_service import __version__


class Settings(BaseSettings):
    # API configuration
    APP_NAME: ClassVar[str] = "CyborgDB Service"
    APP_DESCRIPTION: ClassVar[str] = (
        "REST API for CyborgDB: The Confidential Vector Database"
    )
    APP_VERSION: ClassVar[str] = __version__
    API_VERSION: ClassVar[str] = "v1"

    @property
    def API_PREFIX(self) -> str:
        """Construct the API prefix dynamically based on the API version."""
        return f"/{self.API_VERSION}"

    # Server configuration
    PORT: int = 8000

    # SSL/HTTPS Configuration - simple path-based approach
    SSL_CERT_PATH: Optional[str] = None
    SSL_KEY_PATH: Optional[str] = None

    @property
    def is_https_enabled(self) -> bool:
        """Check if HTTPS is enabled via certificate path"""
        return (
            self.SSL_CERT_PATH is not None
            and self.SSL_KEY_PATH is not None
            and os.path.exists(self.SSL_CERT_PATH)
            and os.path.exists(self.SSL_KEY_PATH)
        )

    # Security
    REQUIRE_API_KEY: bool = True
    CYBORGDB_API_KEY: Optional[str] = None

    # Logging
    CYBORGDB_SERVICE_LOG_LEVEL: str = "INFO"

    # Make sure these are settable via .env file
    CYBORGDB_DB_TYPE: Optional[str] = None
    CYBORGDB_CONNECTION_STRING: Optional[str] = None

    # CyborgDB configuration
    INDEX_LOCATION: Optional[str] = None
    CONFIG_LOCATION: Optional[str] = None
    ITEMS_LOCATION: Optional[str] = None
    INDEX_TABLE_NAME: str = "index"
    CONFIG_TABLE_NAME: str = "config"
    ITEMS_TABLE_NAME: str = "items"
    INDEX_CONNECTION_STRING: Optional[str] = None
    CONFIG_CONNECTION_STRING: Optional[str] = None
    ITEMS_CONNECTION_STRING: Optional[str] = None
    CPU_THREADS: int = 0
    GPU_OPERATIONS: Optional[str] = (
        None  # GPU operations setting (none, upsert, train, query, all, or comma-separated)
    )

    # GPU settings set automatically from GPU_OPERATIONS
    GPU_UPSERT: bool = False
    GPU_TRAIN: bool = False
    GPU_QUERY: bool = False

    # Concurrency settings
    WORKERS: int = 0  # 0 means auto-calculate based on CPU count

    # Replace the Config class with model_config
    model_config = SettingsConfigDict(env_file=".env", extra="ignore")

    # Set fallbacks for locations and connection strings
    @model_validator(mode="after")
    def set_fallbacks(self):
        standalone_components = []
        memory_components = []

        def resolve_location(
            location: str | None,
            connection_string: str | None,
            name: str,  # for error messages
        ) -> tuple[str, str | None]:
            resolved_location = location or self.CYBORGDB_DB_TYPE
            resolved_conn_str = connection_string or self.CYBORGDB_CONNECTION_STRING

            # If still no location, use Standalone default
            if not resolved_location:
                standalone_components.append(name)
                return "rocksdb", None

            # Validate location + connection string compatibility
            if resolved_location == "memory":
                memory_components.append(name)
                return "threadsafememory", None
            elif resolved_location == "standalone":
                standalone_components.append(name)
                # Normalize empty/whitespace strings to None so C++ default path is used
                if resolved_conn_str and resolved_conn_str.strip():
                    return "rocksdb", resolved_conn_str
                return "rocksdb", None
            elif resolved_location in ("redis", "postgres"):
                if not resolved_conn_str:
                    raise ValueError(
                        f"{name}_LOCATION is set to '{resolved_location}' but no connection string is provided. "
                        f"Please set {name}_CONNECTION_STRING or CYBORGDB_CONNECTION_STRING, "
                        f"or unset {name}_LOCATION to use the default standalone database."
                    )
                return resolved_location, resolved_conn_str
            else:
                raise ValueError(
                    f"Invalid {name}_LOCATION: '{resolved_location}'. "
                    f"Must be one of: 'standalone', 'redis', 'postgres', 'memory'"
                )

        # Resolve each location independently
        self.INDEX_LOCATION, self.INDEX_CONNECTION_STRING = resolve_location(
            self.INDEX_LOCATION, self.INDEX_CONNECTION_STRING, "INDEX"
        )
        self.CONFIG_LOCATION, self.CONFIG_CONNECTION_STRING = resolve_location(
            self.CONFIG_LOCATION, self.CONFIG_CONNECTION_STRING, "CONFIG"
        )
        self.ITEMS_LOCATION, self.ITEMS_CONNECTION_STRING = resolve_location(
            self.ITEMS_LOCATION, self.ITEMS_CONNECTION_STRING, "ITEMS"
        )

        # Print info for Standalone usage
        if standalone_components:
            components = ", ".join(standalone_components)
            print(f"\033[96m[INFO] Using standalone storage for: {components}\033[0m")

            standalone_paths = {}
            for comp in standalone_components:
                conn_str = getattr(self, f"{comp}_CONNECTION_STRING")
                standalone_paths[comp] = conn_str

            explicit_paths = {p for p in standalone_paths.values() if p is not None}
            default_components = [c for c, p in standalone_paths.items() if p is None]

            for path in sorted(explicit_paths):
                print(f"   Data will be stored in: {path}/")
            if default_components:
                print(
                    f"   Data will be stored in the default path (~/.cyborgdb/) for: {', '.join(default_components)}"
                )
            print()

        if memory_components:
            components = ", ".join(memory_components)
            print(f"\033[96m[INFO] Using in-memory storage for: {components}\033[0m")
            print("   Data will not persist across restarts.\n")

        # Parse GPU operations
        self.GPU_UPSERT, self.GPU_TRAIN, self.GPU_QUERY = parse_gpu_accelerate(
            self.GPU_OPERATIONS
        )

        return self


def parse_gpu_accelerate(gpu_accelerate: Optional[str]) -> tuple[bool, bool, bool]:
    """Parse the GPU_ACCELERATE environment variable to determine GPU settings."""
    if not gpu_accelerate:
        return False, False, False

    operations = [op.strip() for op in gpu_accelerate.lower().split(",")]

    if "none" in operations:
        return False, False, False
    elif "all" in operations:
        return True, True, True
    else:
        return ("upsert" in operations, "train" in operations, "query" in operations)


def ensure_api_key():
    """Ensure we have an API key, generating one if necessary."""
    # Check environment variable
    if key := os.getenv("SERVICE_API_KEY"):
        return key

    # Check anything else

    # Generate a new API key
    key = secrets.token_urlsafe(32)
    os.environ["SERVICE_API_KEY"] = key

    print("\n   API Key Generated")
    print(f"   Key: {key}")
    print("   Saved to environment variable: SERVICE_API_KEY")
    print("   To disable auth: REQUIRE_API_KEY=false\n")


settings = Settings()
