"""Embedding profile configuration — CLI-side copy.

This module defines the two embedding profiles and the auto-detection logic
independently of the ``indexer`` service package, so the CLI can resolve
a profile even before the indexer is called.

The resolved ``EmbedProfile`` dataclass is a simple, plain Python structure
so it doesn't create a Pydantic dependency at the top level of the CLI.
"""

from __future__ import annotations

import os
from dataclasses import dataclass
from typing import Optional


@dataclass
class EmbedProfile:
    """Resolved embedding profile settings."""

    profile: str
    backend: str
    model_name: str
    dimension: int
    batch_size: int
    max_tokens: int
    description: str


PROFILES: dict[str, EmbedProfile] = {
    "standard": EmbedProfile(
        profile="standard",
        backend="fastembed",
        # INT8 quantized — 3-4x faster than Float32, ~90 MB cache (vs ~270 MB)
        # fastembed activates INT8 ONNX automatically via quantize=True
        model_name="nomic-ai/nomic-embed-text-v1.5",
        dimension=768,
        batch_size=32,      # INT8 allows larger batches
        max_tokens=512,
        description="Best accuracy — daily dev use (~90 MB model, ~2 GB RAM)",
    ),
    "minimal": EmbedProfile(
        profile="minimal",
        backend="fastembed",
        model_name="BAAI/bge-small-en-v1.5",
        dimension=384,
        batch_size=64,
        max_tokens=512,
        description="Low memory — CI / 8 GB RAM laptops (~130 MB model, ~1 GB RAM)",
    ),
}

# Machines with less than this much RAM get auto-downgraded to minimal
_MINIMAL_RAM_THRESHOLD_BYTES = 10 * 1024**3  # 10 GB


def _available_ram_bytes() -> int:
    """Return total system RAM in bytes, or 0 if detection fails."""
    try:
        import psutil  # optional soft-dep

        return psutil.virtual_memory().total  # type: ignore[no-any-return]
    except Exception:
        pass
    try:
        with open("/proc/meminfo") as f:
            for line in f:
                if line.startswith("MemTotal:"):
                    return int(line.split()[1]) * 1024
    except Exception:
        pass
    return 0


def _read_saved_profile() -> str:
    """Read the user's saved profile from ~/.linear-canal/profile.json, or ''."""
    try:
        import json
        from pathlib import Path
        data = json.loads((Path.home() / ".linear-canal" / "profile.json").read_text())
        return data.get("profile", "")
    except Exception:
        return ""


def resolve_embedding_config(profile: Optional[str] = None) -> EmbedProfile:
    """Return the correct EmbedProfile.

    Resolution order:
    1. Explicit ``profile`` argument (``'standard'`` or ``'minimal'``).
    2. ``LC_PROFILE`` environment variable.
    3. Saved profile in ``~/.linear-canal/profile.json`` (set by ``setup`` or ``profile set``).
    4. Auto-detect from system RAM:
       - RAM < 10 GB  →  ``minimal``
       - RAM ≥ 10 GB  →  ``standard``
       - Detection failed  →  ``standard`` (safe fallback)
    """
    chosen = (profile or os.environ.get("LC_PROFILE", "") or _read_saved_profile()).strip().lower()

    if not chosen or chosen not in PROFILES:
        ram = _available_ram_bytes()
        chosen = "minimal" if (0 < ram < _MINIMAL_RAM_THRESHOLD_BYTES) else "standard"

    return PROFILES[chosen]
