import os
import yaml
from enum import Enum
from typing import Optional
from pathlib import Path
from dotenv import load_dotenv
from pydantic import BaseModel, Field, SecretStr

# Load environment variables from .env file
# M6: Use absolute path relative to this file to ensure it's found across different deployment methods
env_path = Path(__file__).parent / ".env"
load_dotenv(dotenv_path=env_path)

# --- 1. Define the Configuration Schema ---

class ApplicationSettings(BaseModel):
    project_name: str = "Cortex Hub"
    version: str = "1.0.0"
    log_level: str = "INFO"
    super_admins: list[str] = Field(default_factory=list)
    allow_password_login: bool = True

class OIDCSettings(BaseModel):
    enabled: bool = False
    client_id: str = ""
    client_secret: str = ""
    server_url: str = ""
    redirect_uri: str = ""
    allow_oidc_login: bool = False

class DatabaseSettings(BaseModel):
    mode: str = "sqlite"
    url: Optional[str] = None
    local_path: str = "data/ai_hub.db"

class ProviderSettings(BaseModel):
    """Generic structure to hold any provider-specific config (api_key, model, etc.)"""
    active_provider: Optional[str] = None
    providers: dict[str, dict] = Field(default_factory=dict)
    # Compatibility for top-level keys
    api_key: Optional[SecretStr] = None
    model_name: Optional[str] = None
    voice_name: Optional[str] = None

class EmbeddingProviderSettings(BaseModel):
    provider: str = "google_gemini"
    model_name: str = "models/text-embedding-004"
    api_key: Optional[SecretStr] = None

class LLMProvidersSettings(BaseModel):
    """Holds shared API keys and per-provider overrides."""
    providers: dict[str, dict] = Field(default_factory=dict)

class VectorStoreSettings(BaseModel):
    index_path: str = "data/faiss_index.bin"
    embedding_dimension: int = 768

class SwarmSettings(BaseModel):
    external_endpoint: Optional[str] = None
    grpc_endpoint: Optional[str] = None

class JournalSettings(BaseModel):
    stream_head_chars: int = 10000
    stream_tail_chars: int = 30000
    thought_head_count: int = 5
    thought_tail_count: int = 15

class AppConfig(BaseModel):
    """Top-level Pydantic model for application configuration."""
    application: ApplicationSettings = Field(default_factory=ApplicationSettings)
    database: DatabaseSettings = Field(default_factory=DatabaseSettings)
    journal: JournalSettings = Field(default_factory=JournalSettings)
    llm_providers: dict[str, dict] = Field(default_factory=dict)
    active_llm_provider: Optional[str] = None
    vector_store: VectorStoreSettings = Field(default_factory=VectorStoreSettings)
    embedding_provider: EmbeddingProviderSettings = Field(default_factory=EmbeddingProviderSettings)
    tts_providers: dict[str, dict] = Field(default_factory=dict)
    active_tts_provider: Optional[str] = None
    stt_providers: dict[str, dict] = Field(default_factory=dict)
    active_stt_provider: Optional[str] = None
    oidc: OIDCSettings = Field(default_factory=OIDCSettings)
    swarm: SwarmSettings = Field(default_factory=SwarmSettings)


# --- 2. Create the Final Settings Object ---
class Settings:
    """
    Holds all application settings, validated and structured by Pydantic.
    Priority Order: Environment Variables > YAML File > Pydantic Defaults
    """
    def __init__(self):
        config_path = os.getenv("CONFIG_PATH", "config.yaml")
        yaml_data = {}
        if os.path.exists(config_path):
            print(f"✅ Loading configuration from {config_path}")
            with open(config_path, 'r') as f:
                yaml_data = yaml.safe_load(f) or {}
        else:
            print(f"⚠️ '{config_path}' not found. Using defaults and environment variables.")
        
        config_from_pydantic = AppConfig.parse_obj(yaml_data)

        def get_from_yaml(keys):
            d = yaml_data
            for key in keys:
                d = d.get(key) if isinstance(d, dict) else None
            return d

        self.PROJECT_NAME: str = os.getenv("PROJECT_NAME") or \
                                 get_from_yaml(["application", "project_name"]) or \
                                 config_from_pydantic.application.project_name
        self.VERSION: str = config_from_pydantic.application.version
        self.LOG_LEVEL: str = os.getenv("LOG_LEVEL") or \
                                 get_from_yaml(["application", "log_level"]) or \
                                 config_from_pydantic.application.log_level
        super_admins_env = os.getenv("SUPER_ADMINS")
        self.SUPER_ADMINS: list[str] = [x.strip() for x in super_admins_env.split(",")] if super_admins_env else \
                                        get_from_yaml(["application", "super_admins"]) or \
                                        config_from_pydantic.application.super_admins
        # --- OIDC Settings ---
        self.OIDC_ENABLED: bool = str(os.getenv("OIDC_ENABLED") if os.getenv("OIDC_ENABLED") is not None else
                                    get_from_yaml(["oidc", "enabled"]) if get_from_yaml(["oidc", "enabled"]) is not None else
                                    config_from_pydantic.oidc.enabled).lower() == "true"
        self.OIDC_CLIENT_ID: str = os.getenv("OIDC_CLIENT_ID") or \
                                   get_from_yaml(["oidc", "client_id"]) or \
                                   config_from_pydantic.oidc.client_id
        self.OIDC_CLIENT_SECRET: str = os.getenv("OIDC_CLIENT_SECRET") or \
                                       get_from_yaml(["oidc", "client_secret"]) or \
                                       config_from_pydantic.oidc.client_secret
        self.OIDC_SERVER_URL: str = os.getenv("OIDC_SERVER_URL") or \
                                     get_from_yaml(["oidc", "server_url"]) or \
                                     config_from_pydantic.oidc.server_url
        self.OIDC_REDIRECT_URI: str = os.getenv("OIDC_REDIRECT_URI") or \
                                       get_from_yaml(["oidc", "redirect_uri"]) or \
                                       config_from_pydantic.oidc.redirect_uri
        self.ALLOW_OIDC_LOGIN: bool = str(os.getenv("ALLOW_OIDC_LOGIN") if os.getenv("ALLOW_OIDC_LOGIN") is not None else
                                        get_from_yaml(["oidc", "allow_oidc_login"]) if get_from_yaml(["oidc", "allow_oidc_login"]) is not None else
                                        config_from_pydantic.oidc.allow_oidc_login).lower() == "true"

        self.ALLOW_PASSWORD_LOGIN: bool = (str(os.getenv("ALLOW_PASSWORD_LOGIN") if os.getenv("ALLOW_PASSWORD_LOGIN") is not None else
                                            get_from_yaml(["application", "allow_password_login"]) if get_from_yaml(["application", "allow_password_login"]) is not None else
                                            config_from_pydantic.application.allow_password_login).lower() == "true") or (not self.OIDC_ENABLED)
        
        # --- Swarm Settings ---
        self.GRPC_EXTERNAL_ENDPOINT: Optional[str] = os.getenv("GRPC_EXTERNAL_ENDPOINT") or \
                                                      get_from_yaml(["swarm", "external_endpoint"]) or \
                                                      config_from_pydantic.swarm.external_endpoint
                                                      
        self.GRPC_TARGET_ORIGIN: Optional[str] = os.getenv("GRPC_TARGET_ORIGIN") or \
                                                      os.getenv("HUB_GRPC_ENDPOINT") or \
                                                      get_from_yaml(["swarm", "grpc_endpoint"]) or \
                                                      config_from_pydantic.swarm.grpc_endpoint
        
        # Infer TLS from endpoint or port
        protocol = self.GRPC_EXTERNAL_ENDPOINT.split("://")[0] if self.GRPC_EXTERNAL_ENDPOINT and "://" in self.GRPC_EXTERNAL_ENDPOINT else "http"
        self.GRPC_TLS_ENABLED: bool = bool((protocol == "https") or (self.GRPC_TARGET_ORIGIN and ":443" in self.GRPC_TARGET_ORIGIN))
        
        # Legacy paths (no longer in UI, but kept for env var parity if needed)
        self.GRPC_CERT_PATH: Optional[str] = os.getenv("GRPC_CERT_PATH") or get_from_yaml(["swarm", "cert_path"])
        self.GRPC_KEY_PATH: Optional[str] = os.getenv("GRPC_KEY_PATH") or get_from_yaml(["swarm", "key_path"])
        
        self.SECRET_KEY: str = os.getenv("SECRET_KEY") or \
                               get_from_yaml(["application", "secret_key"]) or \
                               self.OIDC_CLIENT_SECRET or "dev-secret-key-1337"

        # --- Directory Settings ---
        self.DATA_DIR: str = os.getenv("DATA_DIR") or \
                             get_from_yaml(["application", "data_dir"]) or \
                             "/app/data" # Hardcoded default for production Docker deployment
        self.SKILLS_DIR: str = os.path.join(self.DATA_DIR, "skills")

        self.BROWSER_SERVICE_ENDPOINT: str = os.getenv("BROWSER_SERVICE_ENDPOINT", "browser-service:50052")
        self.GRPC_PORT: int = int(os.getenv("GRPC_PORT", "50051"))
        self.MIRROR_MAX_WORKERS: int = int(os.getenv("MIRROR_MAX_WORKERS", "32"))
        
        cors_raw = os.getenv("CORS_ORIGINS")
        self.CORS_ORIGINS: list[str] = cors_raw.split(",") if cors_raw else []
        self.HUB_PUBLIC_URL: Optional[str] = os.getenv("HUB_PUBLIC_URL")

        # --- OS-aware Agent Provisioning Defaults ---
        self.AGENT_SYNC_ROOT_LINUX: str = os.getenv("AGENT_SYNC_ROOT_LINUX", "/tmp/cortex-sync")
        self.AGENT_SYNC_ROOT_WINDOWS: str = os.getenv("AGENT_SYNC_ROOT_WINDOWS", "C:\\CortexAgent\\sync")
        self.AGENT_FS_ROOT_LINUX: str = os.getenv("AGENT_FS_ROOT_LINUX", "/")
        self.AGENT_FS_ROOT_WINDOWS: str = os.getenv("AGENT_FS_ROOT_WINDOWS", "C:\\")

        # --- Journal Settings ---
        self.STREAM_HEAD_CHARS: int = int(os.getenv("STREAM_HEAD_CHARS") or \
                                          get_from_yaml(["journal", "stream_head_chars"]) or \
                                          config_from_pydantic.journal.stream_head_chars)
        self.STREAM_TAIL_CHARS: int = int(os.getenv("STREAM_TAIL_CHARS") or \
                                          get_from_yaml(["journal", "stream_tail_chars"]) or \
                                          config_from_pydantic.journal.stream_tail_chars)
        self.THOUGHT_HEAD_COUNT: int = int(os.getenv("THOUGHT_HEAD_COUNT") or \
                                           get_from_yaml(["journal", "thought_head_count"]) or \
                                           config_from_pydantic.journal.thought_head_count)
        self.THOUGHT_TAIL_COUNT: int = int(os.getenv("THOUGHT_TAIL_COUNT") or \
                                           get_from_yaml(["journal", "thought_tail_count"]) or \
                                           config_from_pydantic.journal.thought_tail_count)

        # --- Database Settings ---
        self.DB_MODE: str = os.getenv("DB_MODE") or \
                                get_from_yaml(["database", "mode"]) or \
                                config_from_pydantic.database.mode

        local_db_path = os.getenv("LOCAL_DB_PATH") or \
                                 get_from_yaml(["database", "local_path"]) or \
                                 config_from_pydantic.database.local_path
        external_db_url = os.getenv("DATABASE_URL") or \
                                 get_from_yaml(["database", "url"]) or \
                                 config_from_pydantic.database.url

        if self.DB_MODE == "sqlite":
            normalized_path = local_db_path.lstrip("./")
            if external_db_url and external_db_url.startswith("sqlite"):
                self.DATABASE_URL: str = external_db_url
            else:
                self.DATABASE_URL: str = f"sqlite:///./{normalized_path}" if normalized_path else "sqlite:///./data/ai_hub.db"
        else:
            self.DATABASE_URL: str = external_db_url or "sqlite:///./data/ai_hub.db" 
        
        # --- Agnostic Provider Resolution ---
        # We store everything in a flat map for the legacy settings getters, 
        # but also provide a dynamic map.
        
        self.LLM_PROVIDERS = config_from_pydantic.llm_providers or {}
        # Support legacy environment variables and merge them into the providers map
        for env_key, env_val in os.environ.items():
            if env_key.endswith("_API_KEY") and not any(x in env_key for x in ["TTS", "STT", "EMBEDDING"]):
                provider_id = env_key.replace("_API_KEY", "").lower()
                if provider_id not in self.LLM_PROVIDERS:
                    self.LLM_PROVIDERS[provider_id] = {}
                self.LLM_PROVIDERS[provider_id]["api_key"] = env_val
            if env_key.endswith("_MODEL_NAME") and not any(x in env_key for x in ["TTS", "STT", "EMBEDDING"]):
                provider_id = env_key.replace("_MODEL_NAME", "").lower()
                if provider_id not in self.LLM_PROVIDERS:
                    self.LLM_PROVIDERS[provider_id] = {}
                self.LLM_PROVIDERS[provider_id]["model"] = env_val

        # Fallback to first available provider, otherwise None
        self.ACTIVE_LLM_PROVIDER = os.getenv("ACTIVE_LLM_PROVIDER") or \
                                     get_from_yaml(["active_llm_provider"]) or \
                                     config_from_pydantic.active_llm_provider or \
                                     (list(self.LLM_PROVIDERS.keys())[0] if self.LLM_PROVIDERS else None)

        # CONFIG_OVERRIDE: When True, config.yaml/env always wins over admin DB preferences.
        # When False (default), DB preferences take precedence; config is only used to fill missing values.
        _override_env = os.getenv("CONFIG_OVERRIDE", "").lower()
        _override_yaml = get_from_yaml(["config_override"])
        if _override_env in ("1", "true", "yes"):
            self.CONFIG_OVERRIDE: bool = True
        elif _override_env in ("0", "false", "no"):
            self.CONFIG_OVERRIDE: bool = False
        elif _override_yaml is not None:
            self.CONFIG_OVERRIDE: bool = bool(_override_yaml)
        else:
            self.CONFIG_OVERRIDE: bool = False

        # 2. Resolve Vector / Embedding
        self.FAISS_INDEX_PATH: str = os.getenv("FAISS_INDEX_PATH") or \
                                             get_from_yaml(["vector_store", "index_path"]) or \
                                             config_from_pydantic.vector_store.index_path
        dimension_str = os.getenv("EMBEDDING_DIMENSION") or \
                                 get_from_yaml(["vector_store", "embedding_dimension"]) or \
                                 config_from_pydantic.vector_store.embedding_dimension
        self.EMBEDDING_DIMENSION: int = int(dimension_str)

        self.EMBEDDING_PROVIDER: str = os.getenv("EMBEDDING_PROVIDER") or \
                                        get_from_yaml(["embedding_provider", "provider"]) or \
                                        config_from_pydantic.embedding_provider.provider
        self.EMBEDDING_MODEL_NAME: str = os.getenv("EMBEDDING_MODEL_NAME") or \
                                             get_from_yaml(["embedding_provider", "model_name"]) or \
                                             config_from_pydantic.embedding_provider.model_name
        self.EMBEDDING_API_KEY: Optional[str] = os.getenv("EMBEDDING_API_KEY") or \
                                                 get_from_yaml(["embedding_provider", "api_key"])

        # 3. Resolve TTS (Agnostic)
        self.TTS_PROVIDERS = config_from_pydantic.tts_providers or {}
        # Legacy single-provider from YAML/Env
        legacy_tts_provider = os.getenv("TTS_PROVIDER") or get_from_yaml(["tts_provider", "provider"])
        if legacy_tts_provider and legacy_tts_provider not in self.TTS_PROVIDERS:
            self.TTS_PROVIDERS[legacy_tts_provider] = {
                "provider": legacy_tts_provider,
                "model_name": os.getenv("TTS_MODEL_NAME") or get_from_yaml(["tts_provider", "model_name"]),
                "voice_name": os.getenv("TTS_VOICE_NAME") or get_from_yaml(["tts_provider", "voice_name"]),
                "api_key": os.getenv("TTS_API_KEY") or get_from_yaml(["tts_provider", "api_key"]),
            }
        
        self.TTS_PROVIDER: str = legacy_tts_provider or \
                                 config_from_pydantic.active_tts_provider or \
                                 (list(self.TTS_PROVIDERS.keys())[0] if self.TTS_PROVIDERS else "google_gemini")
        
        # Legacy back-compat fields for the active one
        active_tts = self.TTS_PROVIDERS.get(self.TTS_PROVIDER, {})
        self.TTS_VOICE_NAME: str = active_tts.get("voice_name") or "Kore"
        self.TTS_MODEL_NAME: str = active_tts.get("model_name") # No hardcoded default
        self.TTS_API_KEY: Optional[str] = active_tts.get("api_key")

        # 4. Resolve STT (Agnostic)
        self.STT_PROVIDERS = config_from_pydantic.stt_providers or {}
        legacy_stt_provider = os.getenv("STT_PROVIDER") or get_from_yaml(["stt_provider", "provider"])
        if legacy_stt_provider and legacy_stt_provider not in self.STT_PROVIDERS:
            self.STT_PROVIDERS[legacy_stt_provider] = {
                "provider": legacy_stt_provider,
                "model_name": os.getenv("STT_MODEL_NAME") or get_from_yaml(["stt_provider", "model_name"]),
                "api_key": os.getenv("STT_API_KEY") or get_from_yaml(["stt_provider", "api_key"]),
            }

        self.STT_PROVIDER: str = legacy_stt_provider or \
                                 config_from_pydantic.active_stt_provider or \
                                 (list(self.STT_PROVIDERS.keys())[0] if self.STT_PROVIDERS else "google_gemini")
        
        active_stt = self.STT_PROVIDERS.get(self.STT_PROVIDER, {})
        self.STT_MODEL_NAME: str = active_stt.get("model_name") # No hardcoded default
        self.STT_API_KEY: Optional[str] = active_stt.get("api_key")
        
    def save_to_yaml(self):
        """Saves current settings back to config.yaml."""
        import yaml
        config_path = os.getenv("CONFIG_PATH", "config.yaml")

        def get_val(v):
            if hasattr(v, "get_secret_value"):
                return v.get_secret_value()
            return v

        # Build data dictionary by mapping current class attributes back to YAML structure
        # This keeps the sync logic centralized in this class
        data = {
            "application": {
                "project_name": self.PROJECT_NAME,
                "version": self.VERSION,
                "log_level": self.LOG_LEVEL,
                "super_admins": self.SUPER_ADMINS,
                "allow_password_login": self.ALLOW_PASSWORD_LOGIN
            },
            "database": {
                "mode": self.DB_MODE,
                "local_path": self.DATABASE_URL.replace("sqlite:///./", "") if "sqlite" in self.DATABASE_URL else "data/ai_hub.db"
            },
            "vector_store": {
                "index_path": self.FAISS_INDEX_PATH,
                "embedding_dimension": self.EMBEDDING_DIMENSION
            },
            "oidc": {
                "enabled": self.OIDC_ENABLED,
                "client_id": self.OIDC_CLIENT_ID,
                "client_secret": self.OIDC_CLIENT_SECRET,
                "server_url": self.OIDC_SERVER_URL,
                "redirect_uri": self.OIDC_REDIRECT_URI,
                "allow_oidc_login": self.ALLOW_OIDC_LOGIN
            },
            "swarm": {
                "external_endpoint": self.GRPC_EXTERNAL_ENDPOINT
            },
            "journal": {
                "stream_head_chars": self.STREAM_HEAD_CHARS,
                "stream_tail_chars": self.STREAM_TAIL_CHARS,
                "thought_head_count": self.THOUGHT_HEAD_COUNT,
                "thought_tail_count": self.THOUGHT_TAIL_COUNT
            }
        }
        
        # Ensure directories exist
        os.makedirs(os.path.dirname(os.path.abspath(config_path)), exist_ok=True)
        
        with open(config_path, 'w') as f:
            yaml.dump(data, f, sort_keys=False, default_flow_style=False)
        print(f"🏠 Configuration synchronized to {config_path}")

# Instantiate the single settings object for the application
settings = Settings()