"""Agent config, all from env (the control container is configured at run)."""
# Lazy annotations so the `from_env(cls) -> Config` self-reference is a string,
# not evaluated at class-definition time — otherwise it NameErrors on the agent's
# Python 3.10 (CI lints on 3.14, where PEP 649 hides this).
from __future__ import annotations

import os
from dataclasses import dataclass


def _bool_env(name: str, default: str = "") -> bool:
    """A boolean env var — present + truthy ('1'/'true'/'yes') → True."""
    return os.environ.get(name, default).lower() in ("1", "true", "yes")


@dataclass
class Config:
    fc_url: str          # base URL of the FabledCurator web service
    token: str           # the bearer token from Settings → Tagging → GPU agent
    agent_id: str        # identifies this agent's leases
    batch_size: int      # jobs a worker leases per round
    concurrency: int     # INITIAL parallel workers (tunable live from the UI)
    ccip_model: str      # imgutils CCIP model name ("" → imgutils default)
    detector_level: str  # imgutils person-detector level: n|s|m|x
    poll_idle_seconds: float   # wait between empty leases
    embed_dtype: str     # torch dtype for the crop embedder: float16|float32
    embed_model_override: str  # force a SigLIP-family model ("" → use the one
    #                            the server announces in the lease)
    auto_start: bool     # start the worker pool on boot (so a container restart
    #                      resumes processing without anyone clicking Start)
    auto_scale: bool     # autoscale the worker count (throughput hill-climb)
    # Crop PROPOSERS (extra YOLO detectors that say where to crop). Each weight
    # spec is an ultralytics name | http(s) URL | "hf_repo::file" ("" = off).
    person_weights: str  # general COCO person detector (Western/realistic figs)
    person_conf: float
    anatomy_weights: str  # booru_yolo anime/furry/NSFW components
    anatomy_conf: float
    panel_weights: str    # comic-panel detector
    panel_conf: float
    max_components: int   # cap anatomy component crops per frame
    max_panels: int       # cap panel crops per frame
    max_figures: int      # cap figure boxes per frame (each = a CCIP call + crop)
    max_regions: int      # hard cap on total regions per JOB (submit-size backstop)
    dedupe_iou: float     # crops overlapping >= this (same kind) are near-dupes,
    #                       dropped before the embed; >=1.0 disables it
    frame_dedupe_distance: int  # video frames whose dHash differs by < this many
    #                             bits are near-duplicates, dropped before detect;
    #                             higher keeps more frames, 0 disables
    ffmpeg_timeout: float  # hard ceiling (s) for ffmpeg-from-URL video sampling;
    #                        generous so a SLOW media link still completes
    bandwidth_limit_mb_s: float  # aggregate download cap in MEGABYTES/s across
    #                              all downloaders + video streams (0 = unlimited);
    #                              tunable live from the agent UI
    idle_unload_seconds: float   # after this long with the GPU idle (nothing in
    #                              flight, queue empty or Stopped), unload the
    #                              SigLIP embedder + YOLO proposers to free their
    #                              VRAM; they reload lazily on the next job. A
    #                              24/7 agent otherwise squats on ~5GB doing
    #                              nothing. 0 disables (keep models warm forever).

    @classmethod
    def from_env(cls) -> Config:
        return cls(
            fc_url=os.environ.get("FC_URL", "http://localhost:8000").rstrip("/"),
            token=os.environ.get("FC_TOKEN", ""),
            agent_id=os.environ.get("AGENT_ID", "desktop-agent"),
            batch_size=int(os.environ.get("BATCH_SIZE", "4")),
            concurrency=int(os.environ.get("CONCURRENCY", "1")),
            ccip_model=os.environ.get("CCIP_MODEL", ""),
            detector_level=os.environ.get("DETECTOR_LEVEL", "m"),
            poll_idle_seconds=float(os.environ.get("POLL_IDLE_SECONDS", "10")),
            embed_dtype=os.environ.get("SIGLIP_DTYPE", "float16"),
            embed_model_override=os.environ.get("EMBED_MODEL_NAME", ""),
            auto_start=_bool_env("AUTO_START"),
            auto_scale=_bool_env("AUTO_SCALE", "true"),
            person_weights=os.environ.get("PERSON_WEIGHTS", "yolo11n.pt"),
            person_conf=float(os.environ.get("PERSON_CONF", "0.35")),
            anatomy_weights=os.environ.get("ANATOMY_WEIGHTS", ""),
            anatomy_conf=float(os.environ.get("ANATOMY_CONF", "0.30")),
            panel_weights=os.environ.get("PANEL_WEIGHTS", ""),
            panel_conf=float(os.environ.get("PANEL_CONF", "0.30")),
            max_components=int(os.environ.get("MAX_COMPONENTS", "8")),
            max_panels=int(os.environ.get("MAX_PANELS", "8")),
            max_figures=int(os.environ.get("MAX_FIGURES", "8")),
            max_regions=int(os.environ.get("MAX_REGIONS", "128")),
            dedupe_iou=float(os.environ.get("DEDUPE_IOU", "0.85")),
            frame_dedupe_distance=int(os.environ.get("FRAME_DEDUPE_DISTANCE", "8")),
            ffmpeg_timeout=float(os.environ.get("FFMPEG_TIMEOUT", "1200")),
            # Default 8 MB/s (~64 Mbit/s): ~20% of the measured ~300 Mbit/s home
            # WiFi, so browsing stays snappy while the agent works — yet MORE
            # sweep throughput than the self-inflicted congestion collapse this
            # replaces (2026-07-02: 8 unthrottled downloaders bufferbloated the
            # link to ~1-1.5 MB/s per stream, browser included). Raise it (or 0)
            # from the agent UI on wired/faster networks.
            bandwidth_limit_mb_s=float(os.environ.get("BANDWIDTH_LIMIT_MB_S", "8")),
            # 5 min: long enough that a lull between job bursts doesn't thrash the
            # (few-second) reload, short enough that an agent left running with an
            # empty queue hands its VRAM back promptly.
            idle_unload_seconds=float(os.environ.get("IDLE_UNLOAD_SECONDS", "300")),
        )
