"""Environment configuration loader for CodexLens.

Loads .env files from workspace .codexlens directory with fallback to project root.
Provides unified access to API configurations.

Priority order:
1. Environment variables (already set)
2. .codexlens/.env (workspace-local)
3. .env (project root)
"""

from __future__ import annotations

import logging
import os
from pathlib import Path
from typing import Any, Dict, Optional

log = logging.getLogger(__name__)

# Supported environment variables with descriptions
ENV_VARS = {
    # Reranker configuration (overrides settings.json)
    "RERANKER_MODEL": "Reranker model name (overrides settings.json)",
    "RERANKER_BACKEND": "Reranker backend: fastembed, onnx, api, litellm, legacy",
    "RERANKER_USE_GPU": "Use GPU for local reranker backends: true/false",
    "RERANKER_ENABLED": "Enable reranker: true/false",
    "RERANKER_API_KEY": "API key for reranker service (SiliconFlow/Cohere/Jina)",
    "RERANKER_API_BASE": "Base URL for reranker API (overrides provider default)",
    "RERANKER_PROVIDER": "Reranker provider: siliconflow, cohere, jina",
    "RERANKER_POOL_ENABLED": "Enable reranker high availability pool: true/false",
    "RERANKER_STRATEGY": "Reranker load balance strategy: round_robin, latency_aware, weighted_random",
    "RERANKER_COOLDOWN": "Reranker rate limit cooldown in seconds",
    # Embedding configuration (overrides settings.json)
    "EMBEDDING_MODEL": "Embedding model/profile name (overrides settings.json)",
    "EMBEDDING_BACKEND": "Embedding backend: fastembed, litellm",
    "AUTO_EMBED_MISSING": "Auto-build embeddings in background when indexed projects are searched without vectors: true/false",
    "EMBEDDING_API_KEY": "API key for embedding service",
    "EMBEDDING_API_BASE": "Base URL for embedding API",
    "EMBEDDING_POOL_ENABLED": "Enable embedding high availability pool: true/false",
    "EMBEDDING_STRATEGY": "Embedding load balance strategy: round_robin, latency_aware, weighted_random",
    "EMBEDDING_COOLDOWN": "Embedding rate limit cooldown in seconds",
    # LiteLLM configuration
    "LITELLM_API_KEY": "API key for LiteLLM",
    "LITELLM_API_BASE": "Base URL for LiteLLM",
    "LITELLM_MODEL": "LiteLLM model name",
    # General configuration
    "CODEXLENS_DATA_DIR": "Custom data directory path",
    "CODEXLENS_DEBUG": "Enable debug mode (true/false)",
    # Cascade / staged pipeline configuration
    "ENABLE_CASCADE_SEARCH": "Enable cascade search (true/false)",
    "CASCADE_STRATEGY": "Cascade strategy: binary, binary_rerank (alias: hybrid), dense_rerank, staged",
    "CASCADE_COARSE_K": "Cascade coarse_k candidate count (int)",
    "CASCADE_FINE_K": "Cascade fine_k result count (int)",
    "STAGED_STAGE2_MODE": "Staged Stage 2 mode: precomputed, realtime, static_global_graph",
    "STAGED_CLUSTERING_STRATEGY": "Staged clustering strategy: auto, score, path, dir_rr, noop, ...",
    "STAGED_CLUSTERING_MIN_SIZE": "Staged clustering min cluster size (int)",
    "ENABLE_STAGED_RERANK": "Enable staged reranking in Stage 4 (true/false)",
    "STAGED_REALTIME_LSP_TIMEOUT_S": "Realtime LSP expansion timeout budget (float seconds)",
    "STAGED_REALTIME_LSP_DEPTH": "Realtime LSP BFS depth (int)",
    "STAGED_REALTIME_LSP_MAX_NODES": "Realtime LSP max nodes (int)",
    "STAGED_REALTIME_LSP_MAX_SEEDS": "Realtime LSP max seeds (int)",
    "STAGED_REALTIME_LSP_MAX_CONCURRENT": "Realtime LSP max concurrent requests (int)",
    "STAGED_REALTIME_LSP_WARMUP_S": "Realtime LSP warmup wait after didOpen (float seconds)",
    "STAGED_REALTIME_LSP_RESOLVE_SYMBOLS": "Resolve symbols via documentSymbol in realtime expansion (true/false)",
    # Chunking configuration
    "CHUNK_STRIP_COMMENTS": "Strip comments from code chunks for embedding: true/false (default: true)",
    "CHUNK_STRIP_DOCSTRINGS": "Strip docstrings from code chunks for embedding: true/false (default: true)",
    # Search ranking tuning
    "TEST_FILE_PENALTY": "Penalty for test/fixture paths in final search ranking: 0.0-1.0 (default: 0.15)",
    "GENERATED_FILE_PENALTY": "Penalty for generated/build artifact paths in final search ranking: 0.0-1.0 (default: 0.35)",
    # Reranker tuning
    "RERANKER_TEST_FILE_PENALTY": "Penalty for test files in reranking: 0.0-1.0 (default: 0.0)",
    "RERANKER_DOCSTRING_WEIGHT": "Weight for docstring chunks in reranking: 0.0-1.0 (default: 1.0)",
}


def _parse_env_line(line: str) -> tuple[str, str] | None:
    """Parse a single .env line, returning (key, value) or None."""
    line = line.strip()
    
    # Skip empty lines and comments
    if not line or line.startswith("#"):
        return None
    
    # Handle export prefix
    if line.startswith("export "):
        line = line[7:].strip()
    
    # Split on first =
    if "=" not in line:
        return None
    
    key, _, value = line.partition("=")
    key = key.strip()
    value = value.strip()
    
    # Remove surrounding quotes
    if len(value) >= 2:
        if (value.startswith('"') and value.endswith('"')) or \
           (value.startswith("'") and value.endswith("'")):
            value = value[1:-1]
    
    return key, value


def load_env_file(env_path: Path) -> Dict[str, str]:
    """Load environment variables from a .env file.
    
    Args:
        env_path: Path to .env file
        
    Returns:
        Dictionary of environment variables
    """
    if not env_path.is_file():
        return {}
    
    env_vars: Dict[str, str] = {}
    
    try:
        content = env_path.read_text(encoding="utf-8")
        for line in content.splitlines():
            result = _parse_env_line(line)
            if result:
                key, value = result
                env_vars[key] = value
    except (OSError, UnicodeDecodeError) as exc:
        # File access errors or encoding issues are expected and logged
        log.warning("Failed to load .env file %s: %s", env_path, exc)
    except Exception as exc:
        # Other unexpected errors are also logged but indicate a code issue
        log.warning("Unexpected error loading .env file %s: %s", env_path, exc)
    
    return env_vars


def _get_global_data_dir() -> Path:
    """Get global CodexLens data directory."""
    env_override = os.environ.get("CODEXLENS_DATA_DIR")
    if env_override:
        return Path(env_override).expanduser().resolve()
    return (Path.home() / ".codexlens").resolve()


def load_global_env() -> Dict[str, str]:
    """Load environment variables from global ~/.codexlens/.env file.

    Returns:
        Dictionary of environment variables from global config
    """
    global_env_path = _get_global_data_dir() / ".env"
    if global_env_path.is_file():
        env_vars = load_env_file(global_env_path)
        log.debug("Loaded %d vars from global %s", len(env_vars), global_env_path)
        return env_vars
    return {}


def load_workspace_env(workspace_root: Path | None = None) -> Dict[str, str]:
    """Load environment variables from workspace .env files.

    Priority (later overrides earlier):
    1. Global ~/.codexlens/.env (lowest priority)
    2. Project root .env
    3. .codexlens/.env (highest priority)

    Args:
        workspace_root: Workspace root directory. If None, uses current directory.

    Returns:
        Merged dictionary of environment variables
    """
    if workspace_root is None:
        workspace_root = Path.cwd()

    workspace_root = Path(workspace_root).resolve()

    env_vars: Dict[str, str] = {}

    # Load from global ~/.codexlens/.env (lowest priority)
    global_vars = load_global_env()
    if global_vars:
        env_vars.update(global_vars)

    # Load from project root .env (medium priority)
    root_env = workspace_root / ".env"
    if root_env.is_file():
        loaded = load_env_file(root_env)
        env_vars.update(loaded)
        log.debug("Loaded %d vars from %s", len(loaded), root_env)

    # Load from .codexlens/.env (highest priority)
    codexlens_env = workspace_root / ".codexlens" / ".env"
    if codexlens_env.is_file():
        loaded = load_env_file(codexlens_env)
        env_vars.update(loaded)
        log.debug("Loaded %d vars from %s", len(loaded), codexlens_env)

    return env_vars


def apply_workspace_env(workspace_root: Path | None = None, *, override: bool = False) -> int:
    """Load .env files and apply to os.environ.
    
    Args:
        workspace_root: Workspace root directory
        override: If True, override existing environment variables
        
    Returns:
        Number of variables applied
    """
    env_vars = load_workspace_env(workspace_root)
    applied = 0
    
    for key, value in env_vars.items():
        if override or key not in os.environ:
            os.environ[key] = value
            applied += 1
            log.debug("Applied env var: %s", key)
    
    return applied


def get_env(key: str, default: str | None = None, *, workspace_root: Path | None = None) -> str | None:
    """Get environment variable with .env file fallback.
    
    Priority:
    1. os.environ (already set)
    2. .codexlens/.env
    3. .env
    4. default value
    
    Args:
        key: Environment variable name
        default: Default value if not found
        workspace_root: Workspace root for .env file lookup
        
    Returns:
        Value or default
    """
    # Check os.environ first
    if key in os.environ:
        return os.environ[key]
    
    # Load from .env files
    env_vars = load_workspace_env(workspace_root)
    if key in env_vars:
        return env_vars[key]
    
    return default


def get_api_config(
    prefix: str,
    *,
    workspace_root: Path | None = None,
    defaults: Dict[str, Any] | None = None,
) -> Dict[str, Any]:
    """Get API configuration from environment.
    
    Loads {PREFIX}_API_KEY, {PREFIX}_API_BASE, {PREFIX}_MODEL, etc.
    
    Args:
        prefix: Environment variable prefix (e.g., "RERANKER", "EMBEDDING")
        workspace_root: Workspace root for .env file lookup
        defaults: Default values
        
    Returns:
        Dictionary with api_key, api_base, model, etc.
    """
    defaults = defaults or {}
    
    config: Dict[str, Any] = {}
    
    # Standard API config fields
    field_mapping = {
        "api_key": f"{prefix}_API_KEY",
        "api_base": f"{prefix}_API_BASE",
        "model": f"{prefix}_MODEL",
        "provider": f"{prefix}_PROVIDER",
        "timeout": f"{prefix}_TIMEOUT",
    }
    
    for field, env_key in field_mapping.items():
        value = get_env(env_key, workspace_root=workspace_root)
        if value is not None:
            # Type conversion for specific fields
            if field == "timeout":
                try:
                    config[field] = float(value)
                except ValueError:
                    pass
            else:
                config[field] = value
        elif field in defaults:
            config[field] = defaults[field]
    
    return config


def generate_env_example() -> str:
    """Generate .env.example content with all supported variables.
    
    Returns:
        String content for .env.example file
    """
    lines = [
        "# CodexLens Environment Configuration",
        "# Copy this file to .codexlens/.env and fill in your values",
        "",
    ]
    
    # Group by prefix
    groups: Dict[str, list] = {}
    for key, desc in ENV_VARS.items():
        prefix = key.split("_")[0]
        if prefix not in groups:
            groups[prefix] = []
        groups[prefix].append((key, desc))
    
    for prefix, items in groups.items():
        lines.append(f"# {prefix} Configuration")
        for key, desc in items:
            lines.append(f"# {desc}")
            lines.append(f"# {key}=")
        lines.append("")
    
    return "\n".join(lines)
