"""Configuration management for Oclawma.

This module provides YAML-based configuration with support for:
- Model profiles (local, cloud, fallback)
- API key management
- Context budget overrides
- Profile selection
"""

from __future__ import annotations

import os
from dataclasses import asdict, dataclass, field
from pathlib import Path
from typing import Any, Literal

import yaml

DEFAULT_CONFIG_DIR = Path.home() / ".oclawma"
DEFAULT_CONFIG_PATH = DEFAULT_CONFIG_DIR / "config.yaml"


class ConfigurationError(Exception):
    """Raised when there's an error with configuration."""

    pass


@dataclass
class ModelProfile:
    """Configuration for a model profile."""

    name: str
    provider: Literal["ollama", "kimi", "openai", "anthropic"]
    model: str
    base_url: str | None = None
    api_key: str | None = None
    api_key_env: str | None = None
    temperature: float = 0.7
    max_tokens: int | None = None
    top_p: float = 1.0
    enabled: bool = True

    def get_api_key(self) -> str | None:
        """Get API key, checking environment variable if configured."""
        if self.api_key:
            return self.api_key
        if self.api_key_env:
            return os.environ.get(self.api_key_env)
        return None

    def to_dict(self) -> dict[str, Any]:
        """Convert profile to dictionary, excluding plaintext api_key."""
        data = asdict(self)
        # Never serialize plaintext API keys to disk
        data.pop("api_key", None)
        return data

    @classmethod
    def from_dict(cls, name: str, data: dict[str, Any]) -> ModelProfile:
        """Create profile from dictionary."""
        return cls(
            name=name,
            provider=data.get("provider", "ollama"),
            model=data.get("model", "qwen2.5:3b"),
            base_url=data.get("base_url"),
            api_key=data.get("api_key"),
            api_key_env=data.get("api_key_env"),
            temperature=data.get("temperature", 0.7),
            max_tokens=data.get("max_tokens"),
            top_p=data.get("top_p", 1.0),
            enabled=data.get("enabled", True),
        )


@dataclass
class ContextBudgetConfig:
    """Configuration for context budget management."""

    total_budget: int = 8192
    warning_threshold: int = 6144  # 75%
    critical_threshold: int = 7168  # 87%
    strict_mode: bool = True

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return asdict(self)

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> ContextBudgetConfig:
        """Create from dictionary."""
        return cls(
            total_budget=data.get("total_budget", 8192),
            warning_threshold=data.get("warning_threshold", 6144),
            critical_threshold=data.get("critical_threshold", 7168),
            strict_mode=data.get("strict_mode", True),
        )


@dataclass
class FallbackConfig:
    """Configuration for fallback provider behavior."""

    enabled: bool = True
    primary_profile: str = "local"
    fallback_profile: str = "cloud"
    trigger_on_context_overflow: bool = True
    trigger_on_connection_error: bool = True

    def to_dict(self) -> dict[str, Any]:
        """Convert to dictionary."""
        return asdict(self)

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> FallbackConfig:
        """Create from dictionary."""
        return cls(
            enabled=data.get("enabled", True),
            primary_profile=data.get("primary_profile", "local"),
            fallback_profile=data.get("fallback_profile", "cloud"),
            trigger_on_context_overflow=data.get("trigger_on_context_overflow", True),
            trigger_on_connection_error=data.get("trigger_on_connection_error", True),
        )


@dataclass
class Config:
    """Main configuration class for Oclawma."""

    # Active profile name
    active_profile: str = "local"

    # Model profiles
    profiles: dict[str, ModelProfile] = field(default_factory=dict)

    # Context budget configuration
    budget: ContextBudgetConfig = field(default_factory=ContextBudgetConfig)

    # Fallback configuration
    fallback: FallbackConfig = field(default_factory=FallbackConfig)

    # Global settings
    default_timeout: int = 120
    max_retries: int = 3

    def __post_init__(self) -> None:
        """Initialize default profiles if none exist."""
        if not self.profiles:
            self._init_default_profiles()

    def _init_default_profiles(self) -> None:
        """Initialize default model profiles."""
        self.profiles = {
            "local": ModelProfile(
                name="local",
                provider="ollama",
                model="qwen2.5:3b",
                base_url="http://localhost:11434",
                temperature=0.7,
            ),
            "cloud": ModelProfile(
                name="cloud",
                provider="kimi",
                model="kimi-k2-5",
                api_key_env="KIMI_API_KEY",
                temperature=0.7,
            ),
            "fallback": ModelProfile(
                name="fallback",
                provider="kimi",
                model="kimi-k2-5",
                api_key_env="KIMI_API_KEY",
                temperature=0.7,
            ),
        }

    def get_active_profile(self) -> ModelProfile:
        """Get the currently active profile."""
        if self.active_profile not in self.profiles:
            raise ConfigurationError(
                f"Active profile '{self.active_profile}' not found. "
                f"Available profiles: {list(self.profiles.keys())}"
            )
        return self.profiles[self.active_profile]

    def set_active_profile(self, name: str) -> None:
        """Set the active profile."""
        if name not in self.profiles:
            raise ConfigurationError(
                f"Profile '{name}' not found. " f"Available profiles: {list(self.profiles.keys())}"
            )
        self.active_profile = name

    def get_profile(self, name: str) -> ModelProfile | None:
        """Get a profile by name."""
        return self.profiles.get(name)

    def add_profile(self, profile: ModelProfile) -> None:
        """Add or update a profile."""
        self.profiles[profile.name] = profile

    def remove_profile(self, name: str) -> bool:
        """Remove a profile. Returns True if removed, False if not found."""
        if name in self.profiles:
            del self.profiles[name]
            # If we removed the active profile, reset to local
            if self.active_profile == name:
                self.active_profile = (
                    "local"
                    if "local" in self.profiles
                    else next(iter(self.profiles.keys()), "local")
                )
            return True
        return False

    def list_profiles(self) -> list[str]:
        """List all profile names."""
        return list(self.profiles.keys())

    def to_dict(self) -> dict[str, Any]:
        """Convert configuration to dictionary."""
        return {
            "active_profile": self.active_profile,
            "profiles": {name: profile.to_dict() for name, profile in self.profiles.items()},
            "budget": self.budget.to_dict(),
            "fallback": self.fallback.to_dict(),
            "default_timeout": self.default_timeout,
            "max_retries": self.max_retries,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> Config:
        """Create configuration from dictionary."""
        profiles = {}
        for name, profile_data in data.get("profiles", {}).items():
            profiles[name] = ModelProfile.from_dict(name, profile_data)

        return cls(
            active_profile=data.get("active_profile", "local"),
            profiles=profiles,
            budget=ContextBudgetConfig.from_dict(data.get("budget", {})),
            fallback=FallbackConfig.from_dict(data.get("fallback", {})),
            default_timeout=data.get("default_timeout", 120),
            max_retries=data.get("max_retries", 3),
        )

    def save(self, path: Path | str | None = None) -> None:
        """Save configuration to YAML file."""
        save_path = Path(path) if path else DEFAULT_CONFIG_PATH

        # Ensure directory exists
        save_path.parent.mkdir(parents=True, exist_ok=True)

        # Convert to dictionary and save
        data = self.to_dict()

        with open(save_path, "w") as f:
            yaml.dump(data, f, default_flow_style=False, sort_keys=False, allow_unicode=True)

    @classmethod
    def load(cls, path: Path | str | None = None) -> Config:
        """Load configuration from YAML file."""
        load_path = Path(path) if path else DEFAULT_CONFIG_PATH

        if not load_path.exists():
            # Return default configuration
            config = cls()
            return config

        with open(load_path) as f:
            data = yaml.safe_load(f)

        if data is None:
            return cls()

        return cls.from_dict(data)

    @classmethod
    def ensure_exists(cls, path: Path | str | None = None) -> Config:
        """Ensure configuration file exists, creating default if not."""
        config_path = Path(path) if path else DEFAULT_CONFIG_PATH

        if not config_path.exists():
            config = cls()
            config.save(config_path)
            return config

        return cls.load(config_path)


def get_config_path() -> Path:
    """Get the default configuration path."""
    return DEFAULT_CONFIG_PATH


def get_config_dir() -> Path:
    """Get the default configuration directory."""
    return DEFAULT_CONFIG_DIR


def get_workspace_dir() -> Path:
    """Get the default workspace directory."""
    return DEFAULT_CONFIG_DIR / "workspace"
