"""Configuration management for OpenClaw trading system.

Provides Pydantic-based configuration schemas with support for:
- YAML/JSON configuration files
- Environment variable overrides (prefix: OPENCLAW_)
- Type validation and defaults
"""

import json
from pathlib import Path
from typing import Any

import yaml
from pydantic import BaseModel, Field, field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict


class CostStructure(BaseModel):
    """Cost structure configuration for trading simulation.

    Tracks various costs associated with running the trading system,
    including LLM API costs and trading fees.
    """

    llm_input_per_1m: float = Field(
        default=2.5,
        description="Cost per 1M input tokens for LLM API ($)",
        gt=0,
    )
    llm_output_per_1m: float = Field(
        default=10.0,
        description="Cost per 1M output tokens for LLM API ($)",
        gt=0,
    )
    market_data_per_call: float = Field(
        default=0.01,
        description="Cost per market data API call ($)",
        ge=0,
    )
    trade_fee_rate: float = Field(
        default=0.001,
        description="Trading fee rate as decimal (e.g., 0.001 = 0.1%)",
        ge=0,
        le=1,
    )


class SurvivalThresholds(BaseModel):
    """Survival status thresholds for portfolio health.

    Multipliers applied to initial capital to determine agent status.
    Example: thriving_multiplier=3.0 means 3x initial capital = thriving.
    """

    thriving_multiplier: float = Field(
        default=3.0,
        description="Portfolio value multiplier for 'thriving' status",
        gt=1,
    )
    stable_multiplier: float = Field(
        default=1.5,
        description="Portfolio value multiplier for 'stable' status",
        gt=1,
    )
    struggling_multiplier: float = Field(
        default=0.8,
        description="Portfolio value multiplier for 'struggling' status",
        gt=0,
        lt=1,
    )
    bankrupt_multiplier: float = Field(
        default=0.1,
        description="Portfolio value multiplier for 'bankrupt' status",
        ge=0,
        lt=1,
    )

    @field_validator("struggling_multiplier")
    @classmethod
    def validate_struggling(cls, v: float, info: Any) -> float:
        """Ensure struggling threshold is less than stable threshold."""
        if "stable_multiplier" in info.data and v >= info.data["stable_multiplier"]:
            raise ValueError("struggling_multiplier must be less than stable_multiplier")
        return v

    @field_validator("bankrupt_multiplier")
    @classmethod
    def validate_bankrupt(cls, v: float, info: Any) -> float:
        """Ensure bankrupt threshold is less than struggling threshold."""
        if "struggling_multiplier" in info.data and v >= info.data["struggling_multiplier"]:
            raise ValueError("bankrupt_multiplier must be less than struggling_multiplier")
        return v


class LLMConfig(BaseModel):
    """LLM provider configuration.

    Configuration for a specific LLM provider (e.g., OpenAI, Anthropic).
    """

    api_key: str | None = Field(
        default=None,
        description="API key for the LLM provider",
    )
    model: str = Field(
        default="gpt-4o",
        description="Model identifier to use",
    )
    base_url: str | None = Field(
        default=None,
        description="Optional custom base URL for API endpoint",
    )
    temperature: float = Field(
        default=0.7,
        description="Sampling temperature",
        ge=0,
        le=2,
    )
    max_tokens: int | None = Field(
        default=None,
        description="Maximum tokens per response",
        gt=0,
    )
    timeout: int = Field(
        default=30,
        description="Request timeout in seconds",
        gt=0,
    )


class OpenClawConfig(BaseSettings):
    """Main configuration class for OpenClaw trading system.

    This is the root configuration that aggregates all sub-configurations.
    Supports loading from YAML/JSON files and environment variable overrides.

    Environment variables use OPENCLAW_ prefix and __ as nested separator.
    Example: OPENCLAW_INITIAL_CAPITAL__TRADER=5000
    """

    model_config = SettingsConfigDict(
        env_prefix="OPENCLAW_",
        env_nested_delimiter="__",
        extra="ignore",
    )

    initial_capital: dict[str, float] = Field(
        default_factory=lambda: {
            "trader": 10000.0,
            "analyst": 5000.0,
            "risk_manager": 5000.0,
        },
        description="Initial capital allocation per agent type ($)",
    )

    cost_structure: CostStructure = Field(
        default_factory=CostStructure,
        description="Cost structure for simulation",
    )

    survival_thresholds: SurvivalThresholds = Field(
        default_factory=SurvivalThresholds,
        description="Portfolio health thresholds",
    )

    llm_providers: dict[str, LLMConfig] = Field(
        default_factory=lambda: {
            "openai": LLMConfig(model="gpt-4o"),
            "anthropic": LLMConfig(model="claude-3-5-sonnet-20241022"),
        },
        description="LLM provider configurations",
    )

    simulation_days: int = Field(
        default=30,
        description="Default simulation duration in trading days",
        gt=0,
    )

    data_dir: Path = Field(
        default=Path("./data"),
        description="Directory for data storage",
    )

    log_level: str = Field(
        default="INFO",
        description="Logging level",
        pattern="^(DEBUG|INFO|WARNING|ERROR|CRITICAL)$",
    )

    @field_validator("initial_capital")
    @classmethod
    def validate_positive_capital(cls, v: dict[str, float]) -> dict[str, float]:
        """Ensure all initial capital values are positive."""
        for agent_type, amount in v.items():
            if amount <= 0:
                raise ValueError(f"Initial capital for {agent_type} must be positive, got {amount}")
        return v


class ConfigLoader:
    """Configuration loader supporting YAML/JSON files and environment variables.

    Usage:
        # Load from default locations
        config = ConfigLoader.load()

        # Load from specific file
        config = ConfigLoader.load("/path/to/config.yaml")

        # Load with environment variable overrides
        config = ConfigLoader.load(env_prefix="OPENCLAW_")
    """

    DEFAULT_CONFIG_PATHS = [
        Path("config/openclaw.yaml"),
        Path("config/openclaw.yml"),
        Path("config/openclaw.json"),
        Path("openclaw.yaml"),
        Path("openclaw.yml"),
        Path("openclaw.json"),
    ]

    @classmethod
    def load(
        cls,
        config_path: str | Path | None = None,
        env_prefix: str = "OPENCLAW_",
    ) -> OpenClawConfig:
        """Load configuration from file and/or environment variables.

        Args:
            config_path: Path to config file (YAML or JSON). If None, searches default locations.
            env_prefix: Prefix for environment variable overrides.

        Returns:
            OpenClawConfig: Validated configuration object.

        Raises:
            FileNotFoundError: If specified config_path doesn't exist.
            ValueError: If config file is invalid or contains validation errors.
        """
        file_config: dict[str, Any] = {}

        # Load from file if specified or found in default locations
        resolved_path = cls._resolve_config_path(config_path)
        if resolved_path:
            file_config = cls._load_file(resolved_path)

        # Build config with file values as defaults, then apply env overrides
        # pydantic-settings handles env var loading automatically
        config = OpenClawConfig(**file_config)

        return config

    @classmethod
    def _resolve_config_path(cls, config_path: str | Path | None = None) -> Path | None:
        """Resolve configuration file path.

        Args:
            config_path: Explicit path or None to search defaults.

        Returns:
            Resolved Path or None if not found.
        """
        if config_path:
            path = Path(config_path)
            if not path.exists():
                raise FileNotFoundError(f"Configuration file not found: {config_path}")
            return path

        # Search default locations
        for path in cls.DEFAULT_CONFIG_PATHS:
            if path.exists():
                return path

        return None

    @classmethod
    def _load_file(cls, path: Path) -> dict[str, Any]:
        """Load configuration from a YAML or JSON file.

        Args:
            path: Path to configuration file.

        Returns:
            Dictionary of configuration values.

        Raises:
            ValueError: If file format is unsupported or content is invalid.
        """
        content = path.read_text(encoding="utf-8")

        if path.suffix in (".yaml", ".yml"):
            try:
                return yaml.safe_load(content) or {}
            except yaml.YAMLError as e:
                raise ValueError(f"Invalid YAML in {path}: {e}") from e

        elif path.suffix == ".json":
            try:
                return json.loads(content) or {}
            except json.JSONDecodeError as e:
                raise ValueError(f"Invalid JSON in {path}: {e}") from e

        else:
            raise ValueError(f"Unsupported config file format: {path.suffix}")

    @classmethod
    def create_default_config(cls, output_path: str | Path) -> Path:
        """Create a default configuration file.

        Args:
            output_path: Where to write the default config.

        Returns:
            Path to created file.
        """
        output_path = Path(output_path)
        output_path.parent.mkdir(parents=True, exist_ok=True)

        config = OpenClawConfig()

        if output_path.suffix in (".yaml", ".yml"):
            content = cls._config_to_yaml(config)
        elif output_path.suffix == ".json":
            content = config.model_dump_json(indent=2)
        else:
            raise ValueError(f"Unsupported output format: {output_path.suffix}")

        output_path.write_text(content, encoding="utf-8")
        return output_path

    @classmethod
    def _config_to_yaml(cls, config: OpenClawConfig) -> str:
        """Convert config to YAML string with comments."""
        data = config.model_dump()

        # Custom YAML representation with comments
        lines = [
            "# OpenClaw Trading System Configuration",
            "",
            "# Initial capital allocation per agent type ($)",
            "initial_capital:",
        ]
        for agent, amount in data["initial_capital"].items():
            lines.append(f"  {agent}: {amount}")

        lines.extend([
            "",
            "# Cost structure for simulation",
            "cost_structure:",
            f"  llm_input_per_1m: {data['cost_structure']['llm_input_per_1m']}  # Cost per 1M input tokens ($)",
            f"  llm_output_per_1m: {data['cost_structure']['llm_output_per_1m']}  # Cost per 1M output tokens ($)",
            f"  market_data_per_call: {data['cost_structure']['market_data_per_call']}  # Cost per market data API call ($)",
            f"  trade_fee_rate: {data['cost_structure']['trade_fee_rate']}  # Trading fee rate (e.g., 0.001 = 0.1%)",
            "",
            "# Portfolio health thresholds (multipliers of initial capital)",
            "survival_thresholds:",
            f"  thriving_multiplier: {data['survival_thresholds']['thriving_multiplier']}  # 3x = thriving",
            f"  stable_multiplier: {data['survival_thresholds']['stable_multiplier']}  # 1.5x = stable",
            f"  struggling_multiplier: {data['survival_thresholds']['struggling_multiplier']}  # 0.8x = struggling",
            f"  bankrupt_multiplier: {data['survival_thresholds']['bankrupt_multiplier']}  # 0.1x = bankrupt",
            "",
            "# LLM provider configurations",
            "llm_providers:",
        ])

        for provider, settings in data["llm_providers"].items():
            lines.append(f"  {provider}:")
            for key, value in settings.items():
                if value is not None:
                    if key == "api_key":
                        lines.append(f"    {key}: null  # Set via OPENCLAW_LLM_PROVIDERS__{provider.upper()}__API_KEY")
                    else:
                        lines.append(f"    {key}: {value}")

        lines.extend([
            "",
            "# Simulation settings",
            f"simulation_days: {data['simulation_days']}  # Trading days to simulate",
            f"data_dir: {data['data_dir']}  # Data storage directory",
            f"log_level: {data['log_level']}  # DEBUG, INFO, WARNING, ERROR, CRITICAL",
        ])

        return "\n".join(lines)


# Global config instance for easy access
_config_instance: OpenClawConfig | None = None


def get_config() -> OpenClawConfig:
    """Get the global configuration instance.

    Returns:
        OpenClawConfig: The cached configuration or loads default.
    """
    global _config_instance
    if _config_instance is None:
        _config_instance = ConfigLoader.load()
    return _config_instance


def set_config(config: OpenClawConfig) -> None:
    """Set the global configuration instance.

    Args:
        config: Configuration to set as global.
    """
    global _config_instance
    _config_instance = config


def reload_config(config_path: str | Path | None = None) -> OpenClawConfig:
    """Reload configuration from file.

    Args:
        config_path: Optional path to config file.

    Returns:
        OpenClawConfig: Reloaded configuration.
    """
    global _config_instance
    _config_instance = ConfigLoader.load(config_path)
    return _config_instance
