"""Unified configuration for the Thryve framework."""

from __future__ import annotations

import json
import os
from dataclasses import dataclass, field
from typing import Any, Self


@dataclass
class LLMConfig:
    """Configuration for the LLM backend."""

    backend: str = "openai"  # openai | llama_capp | transformers | ollama
    model: str = "gpt-4o-mini"
    api_key: str | None = None
    base_url: str | None = None
    temperature: float = 0.7
    max_tokens: int | None = None
    vision_enabled: bool = True
    extra: dict[str, Any] = field(default_factory=dict)


@dataclass
class EmbeddingConfig:
    """Configuration for the embedding model."""

    backend: str = "openai"
    model: str = "text-embedding-3-small"
    api_key: str | None = None
    base_url: str | None = None
    dimensions: int | None = None


@dataclass
class MemoryConfig:
    """Configuration for the memory subsystem."""

    storage_path: str = "./data/memory.db"
    short_term_retention_days: int = 7
    compaction_strategy: str = "summarize"
    vector_weight: float = 0.7
    bm25_weight: float = 0.3


@dataclass
class AgentConfig:
    """Configuration for the agent subsystem."""

    max_turns: int = 10
    max_steps_per_turn: int = 5
    enable_auto_continue: bool = True
    loop_detection_window: int = 3


@dataclass
class ContextConfig:
    """Configuration for context management."""

    max_tokens: int = 8000
    compression_strategy: str = "truncate"  # truncate | summarize | archive


@dataclass
class ThryveConfig:
    """Top-level configuration aggregating all sub-configs."""

    llm: LLMConfig = field(default_factory=LLMConfig)
    embedding: EmbeddingConfig = field(default_factory=EmbeddingConfig)
    memory: MemoryConfig = field(default_factory=MemoryConfig)
    agent: AgentConfig = field(default_factory=AgentConfig)
    context: ContextConfig = field(default_factory=ContextConfig)

    # ------------------------------------------------------------------
    # Factory helpers
    # ------------------------------------------------------------------

    @classmethod
    def from_env(cls) -> Self:
        """Build config from environment variables.

        Environment variables are prefixed with ``THRYVE_``.  For example:
        ``THRYVE_LLM_API_KEY``, ``THRYVE_LLM_MODEL``, ``THRYVE_LLM_BACKEND``.
        """
        return cls(
            llm=LLMConfig(
                backend=os.getenv("THRYVE_LLM_BACKEND", "openai"),
                model=os.getenv("THRYVE_LLM_MODEL", "gpt-4o-mini"),
                api_key=os.getenv("THRYVE_LLM_API_KEY") or os.getenv("OPENAI_API_KEY"),
                base_url=os.getenv("THRYVE_LLM_BASE_URL"),
                temperature=float(os.getenv("THRYVE_LLM_TEMPERATURE", "0.7")),
            ),
            embedding=EmbeddingConfig(
                backend=os.getenv("THRYVE_EMBEDDING_BACKEND", "openai"),
                model=os.getenv("THRYVE_EMBEDDING_MODEL", "text-embedding-3-small"),
                api_key=os.getenv("THRYVE_EMBEDDING_API_KEY") or os.getenv("OPENAI_API_KEY"),
                base_url=os.getenv("THRYVE_EMBEDDING_BASE_URL"),
            ),
            memory=MemoryConfig(
                storage_path=os.getenv("THRYVE_MEMORY_PATH", "./data/memory.db"),
            ),
        )

    @classmethod
    def from_file(cls, path: str) -> Self:
        """Load config from a JSON file."""
        with open(path) as f:
            data = json.load(f)
        return cls._from_dict(data)

    @classmethod
    def _from_dict(cls, data: dict[str, Any]) -> Self:
        """Construct from a nested dict (e.g. parsed JSON)."""
        return cls(
            llm=LLMConfig(**data.get("llm", {})),
            embedding=EmbeddingConfig(**data.get("embedding", {})),
            memory=MemoryConfig(**data.get("memory", {})),
            agent=AgentConfig(**data.get("agent", {})),
            context=ContextConfig(**data.get("context", {})),
        )

    def merge(self, other: ThryveConfig) -> ThryveConfig:
        """Return a new config where *other* overrides non-default values in *self*."""
        import dataclasses

        def _merge_dc(base: Any, override: Any) -> Any:
            changes: dict[str, Any] = {}
            for fld in dataclasses.fields(override):
                val = getattr(override, fld.name)
                default = fld.default if fld.default is not dataclasses.MISSING else (
                    fld.default_factory() if fld.default_factory is not dataclasses.MISSING else dataclasses.MISSING
                )
                if val != default:
                    changes[fld.name] = val
            return dataclasses.replace(base, **changes)

        return ThryveConfig(
            llm=_merge_dc(self.llm, other.llm),
            embedding=_merge_dc(self.embedding, other.embedding),
            memory=_merge_dc(self.memory, other.memory),
            agent=_merge_dc(self.agent, other.agent),
            context=_merge_dc(self.context, other.context),
        )
