"""
Configuration management for promptv.
"""
from pathlib import Path
from typing import Optional
import yaml
from pydantic import ValidationError

from promptv.models import Config
from promptv.exceptions import PromptVError


class ConfigManagerError(PromptVError):
    """Base exception for config manager errors."""
    pass


class ConfigManager:
    """
    Manage promptv configuration.
    
    Handles loading, saving, and accessing user configuration settings
    for cache and cost estimation.
    
    Example:
        >>> config_mgr = ConfigManager()
        >>> config = config_mgr.get_config()
        >>> print(f"Cache TTL: {config.cache.ttl_seconds}s")
        >>> 
        >>> # Update config
        >>> config.cache.ttl_seconds = 600
        >>> config_mgr.save_config(config)
    """
    
    DEFAULT_CONFIG = """# promptv configuration file
# This file is automatically generated on first run

# Execution mode settings
execution:
  mode: "local"  # "local" or "cloud"
  cloud:
    api_key: null  # Set your cloud API key here
    project_id: null  # Set your cloud project ID here
    endpoint: "https://api.promptv.io"

# Cache settings
cache:
  enabled: true
  ttl_seconds: 300  # 5 minutes
  max_entries: 100

# Cost estimation settings
cost_estimation:
  confirm_threshold: 0.10  # Confirm if cost > $0.10
  default_output_tokens: 500
  default_model: "gpt-4"
  default_provider: "openai"

# LLM Provider API configurations
llm_providers:
  openai:
    api_base_url: "https://api.openai.com/v1"
    default_model: "gpt-4"
  anthropic:
    api_base_url: "https://api.anthropic.com/v1"
    default_model: "claude-3-5-sonnet-20241022"
  cohere:
    api_base_url: "https://api.cohere.ai/v1"
    default_model: "command-r-plus"
  google:
    api_base_url: "https://generativelanguage.googleapis.com/v1"
    default_model: "gemini-pro"
  together:
    api_base_url: "https://api.together.xyz/v1"
    default_model: "meta-llama/Llama-3-70b-chat-hf"
  custom:
    api_base_url: "http://localhost:8000/v1"
    default_model: "custom-model"
"""
    
    def __init__(self, config_path: Optional[Path] = None):
        """
        Initialize ConfigManager.
        
        Args:
            config_path: Optional path to config file (default: ~/.promptv/.config/config.yaml)
        """
        if config_path:
            self.config_path = config_path
        else:
            base_dir = Path.home() / ".promptv"
            config_dir = base_dir / ".config"
            config_dir.mkdir(parents=True, exist_ok=True)
            self.config_path = config_dir / "config.yaml"
        
        # Ensure config file exists
        if not self.config_path.exists():
            self._create_default_config()
    
    def _create_default_config(self) -> None:
        """Create default configuration file."""
        self.config_path.parent.mkdir(parents=True, exist_ok=True)
        with open(self.config_path, 'w', encoding='utf-8') as f:
            f.write(self.DEFAULT_CONFIG)
    
    def get_config(self) -> Config:
        """
        Load and parse configuration.
        
        Returns:
            Config object with all settings
        
        Raises:
            ConfigManagerError: If config file is invalid
        
        Example:
            >>> config_mgr = ConfigManager()
            >>> config = config_mgr.get_config()
            >>> print(config.cache.ttl_seconds)
        """
        try:
            with open(self.config_path, 'r', encoding='utf-8') as f:
                data = yaml.safe_load(f)
            
            if data is None:
                # Empty config file, use defaults
                return Config()
            
            return Config(**data)
        
        except FileNotFoundError:
            # Create default if missing
            self._create_default_config()
            return Config()
        
        except ValidationError as e:
            raise ConfigManagerError(f"Invalid configuration: {e}") from e
        
        except yaml.YAMLError as e:
            raise ConfigManagerError(f"Failed to parse config file: {e}") from e
    
    def save_config(self, config: Config) -> None:
        """
        Save configuration to file.
        
        Args:
            config: Config object to save
        
        Raises:
            ConfigManagerError: If save fails
        
        Example:
            >>> config_mgr = ConfigManager()
            >>> config = config_mgr.get_config()
            >>> config.cache.ttl_seconds = 600
            >>> config_mgr.save_config(config)
        """
        try:
            # Convert to dict
            data = config.model_dump()
            
            # Write to file with comments
            with open(self.config_path, 'w', encoding='utf-8') as f:
                f.write("# promptv configuration file\n")
                f.write("# Updated automatically\n\n")
                yaml.safe_dump(data, f, default_flow_style=False, sort_keys=False)
        
        except Exception as e:
            raise ConfigManagerError(f"Failed to save config: {e}") from e
    
    def reset_to_defaults(self) -> Config:
        """
        Reset configuration to defaults.
        
        Returns:
            Default Config object
        
        Example:
            >>> config_mgr = ConfigManager()
            >>> config = config_mgr.reset_to_defaults()
        """
        self._create_default_config()
        return self.get_config()
    
    def update_cache_settings(
        self,
        enabled: Optional[bool] = None,
        ttl_seconds: Optional[int] = None,
        max_entries: Optional[int] = None
    ) -> Config:
        """
        Update cache settings.
        
        Args:
            enabled: Enable/disable cache
            ttl_seconds: Cache TTL in seconds
            max_entries: Maximum cache entries
        
        Returns:
            Updated Config object
        
        Example:
            >>> config_mgr = ConfigManager()
            >>> config = config_mgr.update_cache_settings(ttl_seconds=600)
        """
        config = self.get_config()
        
        if enabled is not None:
            config.cache.enabled = enabled
        if ttl_seconds is not None:
            config.cache.ttl_seconds = ttl_seconds
        if max_entries is not None:
            config.cache.max_entries = max_entries
        
        self.save_config(config)
        return config
    
    def update_cost_settings(
        self,
        confirm_threshold: Optional[float] = None,
        default_output_tokens: Optional[int] = None,
        default_model: Optional[str] = None,
        default_provider: Optional[str] = None
    ) -> Config:
        """
        Update cost estimation settings.
        
        Args:
            confirm_threshold: Cost threshold for confirmation
            default_output_tokens: Default output token estimate
            default_model: Default model name
            default_provider: Default provider name
        
        Returns:
            Updated Config object
        
        Example:
            >>> config_mgr = ConfigManager()
            >>> config = config_mgr.update_cost_settings(
            ...     default_model="gpt-3.5-turbo",
            ...     confirm_threshold=0.20
            ... )
        """
        config = self.get_config()
        
        if confirm_threshold is not None:
            config.cost_estimation.confirm_threshold = confirm_threshold
        if default_output_tokens is not None:
            config.cost_estimation.default_output_tokens = default_output_tokens
        if default_model is not None:
            config.cost_estimation.default_model = default_model
        if default_provider is not None:
            config.cost_estimation.default_provider = default_provider
        
        self.save_config(config)
        return config
    
    def update_execution_mode(
        self,
        mode: Optional[str] = None,
        api_key: Optional[str] = None,
        project_id: Optional[str] = None,
        endpoint: Optional[str] = None
    ) -> Config:
        """
        Update execution mode settings.
        
        Args:
            mode: Execution mode ("local" or "cloud")
            api_key: Cloud API key
            project_id: Cloud project ID
            endpoint: Cloud API endpoint
        
        Returns:
            Updated Config object
        
        Example:
            >>> config_mgr = ConfigManager()
            >>> config = config_mgr.update_execution_mode(
            ...     mode="cloud",
            ...     api_key="your-api-key",
            ...     project_id="your-project-id"
            ... )
        """
        config = self.get_config()
        
        if mode is not None:
            config.execution.mode = mode
        if api_key is not None:
            config.execution.cloud.api_key = api_key
        if project_id is not None:
            config.execution.cloud.project_id = project_id
        if endpoint is not None:
            config.execution.cloud.endpoint = endpoint
        
        self.save_config(config)
        return config