import os
from pathlib import Path
from typing import List, Optional
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict


class MCPServerConfig(BaseSettings):
    """Configuration for a single MCP server."""

    name: str = Field(description="Unique name for the MCP server")
    transport: str = Field(
        default="stdio", description="Transport type: stdio, http, sse"
    )
    command: Optional[str] = Field(
        default=None, description="Command to run for stdio transport"
    )
    args: List[str] = Field(
        default_factory=list, description="Arguments for the command"
    )
    url: Optional[str] = Field(default=None, description="URL for http/sse transport")
    env: dict = Field(
        default_factory=dict, description="Environment variables for the server"
    )
    enabled: bool = Field(default=True, description="Whether this server is enabled")

    model_config = SettingsConfigDict(extra="ignore")


class Settings(BaseSettings):
    """Application settings managed by Pydantic."""

    # Agent Configuration
    AGENT_NAME: str = "AntigravityAgent"
    # Stream Configuration
    STREAM_ENABLED: bool = Field(
        default=False,
        description="Enable streaming for LLM responses via litellm. "
        "Set to true for real-time token streaming.",
    )
    DEBUG_MODE: bool = False
    PROJECT_ROOT: str = Field(
        default_factory=lambda: os.environ.get(
            "WORKSPACE_PATH", str(Path.cwd())
        ),
        description="Absolute path to the user workspace. "
        "Set via --workspace CLI arg or WORKSPACE_PATH env var. "
        "Defaults to current working directory.",
    )

    # External LLM (OpenAI-compatible) Configuration
    OPENAI_BASE_URL: str = Field(
        default="",
        description="Base URL for OpenAI-compatible API (e.g., https://api.openai.com/v1 or http://localhost:11434/v1)",
    )
    OPENAI_API_KEY: str = Field(
        default="",
        description="API key for OpenAI-compatible endpoint. Leave blank if not required.",
    )
    OPENAI_MODEL: str = Field(
        default="gpt-4o-mini",
        description="Default model name for OpenAI-compatible chat completions.",
    )

    # Memory Configuration
    MEMORY_FILE: str = "memory/agent_memory.md"
    MEMORY_SUMMARY_FILE: str = "memory/agent_summary.md"
    ARTIFACTS_DIR: str = Field(
        default="artifacts",
        description="Directory for artifacts and logs. Relative paths are resolved from PROJECT_ROOT.",
    )

    # Antigravity Context Directory
    ANTIGRAVITY_DIR: str = Field(
        default=".antigravity",
        description="Directory for project context files. Relative to PROJECT_ROOT.",
    )

    # MCP Configuration
    MCP_ENABLED: bool = Field(default=False, description="Enable MCP integration")
    MCP_SERVERS_CONFIG: str = Field(
        default="mcp_servers.json", description="Path to MCP servers configuration file"
    )
    MCP_CONNECTION_TIMEOUT: int = Field(
        default=30, description="Timeout in seconds for MCP server connections"
    )
    MCP_TOOL_PREFIX: str = Field(
        default="mcp_", description="Prefix for MCP tool names to avoid conflicts"
    )

    model_config = SettingsConfigDict(
        env_file=str(
            Path(
                os.environ.get("WORKSPACE_PATH", str(Path.cwd()))
            ) / ".env"
        ),
        env_file_encoding="utf-8",
        extra="ignore",
    )

    @property
    def project_root_path(self) -> Path:
        """Return project root as an absolute path."""
        return Path(self.PROJECT_ROOT).expanduser().resolve()

    def resolve_path(self, path_value: str) -> Path:
        """
        Resolve a path value against project root when it is not absolute.

        Args:
            path_value: Relative or absolute file system path.

        Returns:
            Absolute resolved path.
        """
        path = Path(path_value).expanduser()
        if path.is_absolute():
            return path
        return self.project_root_path / path

    @property
    def memory_file_path(self) -> Path:
        """Return the resolved memory file path."""
        return self.resolve_path(self.MEMORY_FILE)

    @property
    def memory_summary_file_path(self) -> Path:
        """Return the resolved memory summary file path."""
        return self.resolve_path(self.MEMORY_SUMMARY_FILE)

    @property
    def antigravity_dir_path(self) -> Path:
        """Return the resolved antigravity context directory path."""
        return self.resolve_path(self.ANTIGRAVITY_DIR)

    @property
    def artifacts_path(self) -> Path:
        """Return the resolved artifacts directory path."""
        return self.resolve_path(self.ARTIFACTS_DIR)


# Lazy global settings — instantiated on first access so that env-var
# overrides in tests take effect.
_settings: Settings | None = None


def get_settings() -> Settings:
    """Return the global Settings instance, creating it on first call.

    Resolves the .env path from the *current* value of WORKSPACE_PATH
    rather than the value frozen at class-definition time. This lets
    runtime workspace upgrades (e.g. via MCP roots protocol) take effect
    after `reset_settings()`.
    """
    global _settings
    if _settings is None:
        env_path = Path(os.environ.get("WORKSPACE_PATH", str(Path.cwd()))) / ".env"
        _settings = Settings(_env_file=str(env_path))
    return _settings


def reset_settings() -> None:
    """Reset the cached settings (useful in tests after changing env vars)."""
    global _settings
    _settings = None


class _SettingsProxy:
    """Transparent proxy so ``from config import settings`` keeps working."""

    def __getattr__(self, name: str):
        return getattr(get_settings(), name)

    def __setattr__(self, name: str, value):
        setattr(get_settings(), name, value)

    def __repr__(self) -> str:
        return repr(get_settings())


settings = _SettingsProxy()
