"""Pydantic v2 config with TOML persistence."""
from __future__ import annotations

import os
from pathlib import Path
from typing import Any, Optional

import toml
from pydantic import BaseModel
from pydantic_settings import BaseSettings, SettingsConfigDict


CONFIG_DIR = Path(os.environ.get("LLM_REFRESH_CONFIG_DIR", "~/.config/llm_refresh")).expanduser()
CONFIG_PATH = CONFIG_DIR / "config.toml"


class ModelSettings(BaseModel):
    name: str = "microsoft/phi-2"
    rank: int = 16
    lora_alpha: int = 32
    lora_dropout: float = 0.05
    target_modules: list[str] = ["q_proj", "v_proj"]


class BufferSettings(BaseModel):
    strategy: str = "reservoir"
    max_size: int = 10_000
    min_replay_ratio: float = 0.3
    n_buckets: int = 64


class TrainingSettings(BaseModel):
    batch_size: int = 4
    gradient_accumulation_steps: int = 4
    learning_rate: float = 2e-4
    warmup_ratio: float = 0.03
    max_seq_length: int = 512


class EvalSettings(BaseModel):
    anchor_size: int = 200
    forgetting_threshold: float = -0.1
    auto_rollback: bool = True
    batch_size: int = 8
    krs_lambda: float = 0.01


class ScheduleSettings(BaseModel):
    interval_hours: float = 24.0
    data_dir: Optional[str] = None
    state_path: Optional[str] = None


class AppConfig(BaseSettings):
    model_config = SettingsConfigDict(
        env_prefix="LLM_REFRESH__",
        env_nested_delimiter="__",
        extra="ignore",
    )

    model: ModelSettings = ModelSettings()
    buffer: BufferSettings = BufferSettings()
    training: TrainingSettings = TrainingSettings()
    eval: EvalSettings = EvalSettings()
    schedule: ScheduleSettings = ScheduleSettings()

    def get_value(self, key: str) -> Any:
        """Dot-notation getter: 'model.name', 'eval.auto_rollback'."""
        parts = key.split(".")
        obj: Any = self
        for part in parts:
            if hasattr(obj, part):
                obj = getattr(obj, part)
            else:
                raise KeyError(f"Unknown config key: {key!r}")
        return obj

    def set_value(self, key: str, value: str) -> None:
        """Dot-notation setter with type coercion."""
        parts = key.split(".")
        if len(parts) < 2:
            raise KeyError(f"Key must be in section.field format, got: {key!r}")
        section, field_name = parts[0], ".".join(parts[1:])
        if not hasattr(self, section):
            raise KeyError(f"Unknown section: {section!r}")
        section_obj = getattr(self, section)
        if not hasattr(section_obj, field_name):
            raise KeyError(f"Unknown field {field_name!r} in section {section!r}")

        # Type coercion from string
        current = getattr(section_obj, field_name)
        if isinstance(current, bool):
            coerced: Any = value.lower() in ("true", "1", "yes")
        elif isinstance(current, int):
            coerced = int(value)
        elif isinstance(current, float):
            coerced = float(value)
        elif isinstance(current, list):
            coerced = [v.strip() for v in value.split(",")]
        else:
            coerced = value
        object.__setattr__(section_obj, field_name, coerced)

    def save(self, path: Path = CONFIG_PATH) -> None:
        path.parent.mkdir(parents=True, exist_ok=True)
        data = self.model_dump()
        path.write_text(toml.dumps(data))

    @classmethod
    def load(cls, path: Path = CONFIG_PATH) -> "AppConfig":
        if path.exists():
            data = toml.loads(path.read_text())
            return cls.model_validate(data)
        return cls()


_config: Optional[AppConfig] = None


def get_config() -> AppConfig:
    global _config
    if _config is None:
        _config = AppConfig.load()
    return _config


def reset_config() -> None:
    """Reset singleton (useful for tests)."""
    global _config
    _config = None
