"""
Configuration management for TestGen AI.

This module handles all configuration settings including API keys,
model selection, and project-specific settings.
"""

from enum import Enum
from pathlib import Path
from typing import Optional

from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict

# Global config directory in the user's home folder (~/.testgen/.env).
# This is where `testgen config set KEY VALUE` writes keys so they work
# across every project and directory on the machine.
GLOBAL_CONFIG_DIR = Path.home() / ".testgen"
GLOBAL_ENV_FILE = GLOBAL_CONFIG_DIR / ".env"


class LLMProvider(str, Enum):
    """Supported Large Language Model providers.

    TestGen AI provides native support for these platforms, leveraging
    either direct SDKs or the LiteLLM abstraction layer.
    """
    
    OPENAI = "openai"
    ANTHROPIC = "anthropic"
    GEMINI = "gemini"
    OLLAMA = "ollama"


class TestFramework(str, Enum):
    """Supported testing frameworks.

    Defines the specific toolsets used for generating and executing unit tests.
    """
    
    PYTEST = "pytest"
    UNITTEST = "unittest"


class Config(BaseSettings):
    """The central configuration repository for TestGen AI.

    This class manages all runtime settings, from API keys and model
    selection to file scanning rules and reporting preferences. It
    utilizes Pydantic's `BaseSettings` to provide a robust hierarchy of
    configuration sources:

    1. Environment variables (highest priority).
    2. `.env` file within the project root.
    3. Default values specified in the schema.
    """
    
    model_config = SettingsConfigDict(
        # Load order: global file first, then local .env (local wins on conflict).
        # This lets a user run `testgen config set KEY VALUE` once and have it work
        # from any directory, while still allowing per-project overrides via .env.
        env_file=[str(GLOBAL_ENV_FILE), ".env"],
        env_file_encoding="utf-8",
        case_sensitive=False,
        extra="ignore",
    )
    
    # ===== LLM Configuration =====
    openai_api_key: Optional[str] = Field(
        default=None,
        description="OpenAI API key for GPT models"
    )
    
    anthropic_api_key: Optional[str] = Field(
        default=None,
        description="Anthropic API key for Claude models"
    )
    
    gemini_api_key: Optional[str] = Field(
        default=None,
        description="Google Gemini API key"
    )
    
    llm_provider: LLMProvider = Field(
        default=LLMProvider.GEMINI,
        description="LLM provider to use (openai, anthropic, gemini, ollama)"
    )
    
    llm_model: str = Field(
        default="gemini-2.5-flash",
        description="Specific model to use (e.g., gpt-4, claude-3, gemini-2.5-flash, ollama/codellama)"
    )
    
    max_context_tokens: int = Field(
        default=8000,
        description="Maximum tokens to send to LLM",
        ge=1000,
        le=100000
    )
    
    llm_temperature: float = Field(
        default=0.2,
        description="LLM temperature for generation (0.0-1.0)",
        ge=0.0,
        le=1.0
    )
    
    # ===== Test Generation Settings =====
    test_framework: TestFramework = Field(
        default=TestFramework.PYTEST,
        description="Test framework to use"
    )
    
    test_output_dir: Path = Field(
        default=Path("tests"),
        description="Directory to output generated tests"
    )
    
    test_file_prefix: str = Field(
        default="test_",
        description="Prefix for generated test files"
    )
    
    min_coverage_target: int = Field(
        default=80,
        description="Minimum coverage target percentage",
        ge=0,
        le=100
    )
    
    # ===== Scanner Settings =====
    ignore_patterns: list[str] = Field(
        default_factory=lambda: [
            "node_modules/",
            ".git/",
            "__pycache__/",
            ".venv/",
            "venv/",
            ".pytest_cache/",
            "*.pyc",
            ".coverage",
        ],
        description="Patterns to ignore when scanning code"
    )
    
    max_file_size_lines: int = Field(
        default=500,
        description="Max lines before switching to signature extraction only",
        ge=100
    )
    
    supported_extensions: list[str] = Field(
        default_factory=lambda: [".py", ".js", ".ts", ".java"],
        description="File extensions to scan"
    )
    
    # ===== Watch Mode Settings =====
    watch_debounce_seconds: float = Field(
        default=2.0,
        description="Seconds to wait before processing file changes",
        ge=0.1,
        le=10.0
    )
    
    watch_auto_run: bool = Field(
        default=False,
        description="Automatically run tests after generation in watch mode"
    )
    
    # ===== Report Settings =====
    report_output_dir: Path = Field(
        default=Path("reports"),
        description="Directory for test reports"
    )
    
    report_format: str = Field(
        default="html",
        description="Default report format (html, pdf)"
    )
    
    # ===== Execution Settings =====
    pytest_args: list[str] = Field(
        default_factory=lambda: ["--verbose", "--tb=short"],
        description="Default pytest arguments"
    )
    
    playwright_headed: bool = Field(
        default=False,
        description="Run Playwright tests in headed mode"
    )
    
    # ===== Cache Settings =====
    cache_dir: Path = Field(
        default=Path(".testgen-cache"),
        description="Directory for caching scan results and LLM responses"
    )
    
    enable_cache: bool = Field(
        default=True,
        description="Enable caching to reduce costs"
    )
    
    # ===== Logging Settings =====
    log_level: str = Field(
        default="INFO",
        description="Logging level (DEBUG, INFO, WARNING, ERROR)"
    )
    
    log_file: Optional[Path] = Field(
        default=Path(".testgen/logs/testgen.log"),
        description="Log file path"
    )
    
    verbose: bool = Field(
        default=False,
        description="Enable verbose output"
    )
    
    def validate_api_keys(self) -> None:
        """Validate that required API keys are set based on provider."""
        if self.llm_provider == LLMProvider.OPENAI and not self.openai_api_key:
            raise ValueError(
                "OpenAI API key not found.\n"
                "  Fix: testgen config set OPENAI_API_KEY sk-...\n"
                "  Keys are saved globally and work from any directory."
            )
        
        if self.llm_provider == LLMProvider.ANTHROPIC and not self.anthropic_api_key:
            raise ValueError(
                "Anthropic API key not found.\n"
                "  Fix: testgen config set ANTHROPIC_API_KEY sk-ant-...\n"
                "  Keys are saved globally and work from any directory."
            )
        
        if self.llm_provider == LLMProvider.GEMINI and not self.gemini_api_key:
            raise ValueError(
                "Gemini API key not found.\n"
                "  Fix: testgen config set GEMINI_API_KEY AIza...\n"
                "  Get a free key at: https://aistudio.google.com/app/apikey\n"
                "  Keys are saved globally and work from any directory."
            )
    
    def get_api_key(self) -> Optional[str]:
        """Get the appropriate API key based on the selected provider."""
        if self.llm_provider == LLMProvider.OPENAI:
            return self.openai_api_key
        elif self.llm_provider == LLMProvider.ANTHROPIC:
            return self.anthropic_api_key
        elif self.llm_provider == LLMProvider.GEMINI:
            return self.gemini_api_key
        else:  # Ollama doesn't need an API key
            return None
    
    def ensure_directories(self) -> None:
        """Create necessary directories if they don't exist."""
        self.test_output_dir.mkdir(parents=True, exist_ok=True)
        self.report_output_dir.mkdir(parents=True, exist_ok=True)
        self.cache_dir.mkdir(parents=True, exist_ok=True)
        
        if self.log_file:
            self.log_file.parent.mkdir(parents=True, exist_ok=True)


# Global configuration instance
config = Config()
