"""環境変数の読み込み。

すべての設定はプロセス起動時に一度だけ読み込み、以後は不変として扱う。
"""

from __future__ import annotations

import os
from dataclasses import dataclass
from datetime import timedelta, timezone

from . import __version__

#: 日次カウンタの境界に用いるタイムゾーン。
JST = timezone(timedelta(hours=9), name="JST")

PATH_POLICY_DENYLIST = "denylist"
PATH_POLICY_ALLOWLIST = "allowlist"

_TRUE = {"1", "true", "yes", "on"}
_FALSE = {"0", "false", "no", "off"}


class ConfigError(RuntimeError):
    """設定値が不正な場合に送出する。"""


def _env(name: str, default: str = "") -> str:
    value = os.environ.get(name)
    if value is None:
        return default
    return value.strip()


def _env_int(name: str, default: int, *, minimum: int | None = None) -> int:
    raw = _env(name)
    if not raw:
        return default
    try:
        value = int(raw)
    except ValueError as exc:
        raise ConfigError(f"{name} は整数で指定してください: {raw!r}") from exc
    if minimum is not None and value < minimum:
        raise ConfigError(f"{name} は {minimum} 以上で指定してください: {value}")
    return value


def _env_float(name: str, default: float, *, minimum: float | None = None) -> float:
    raw = _env(name)
    if not raw:
        return default
    try:
        value = float(raw)
    except ValueError as exc:
        raise ConfigError(f"{name} は数値で指定してください: {raw!r}") from exc
    if minimum is not None and value < minimum:
        raise ConfigError(f"{name} は {minimum} 以上で指定してください: {value}")
    return value


def _env_bool(name: str, default: bool) -> bool:
    raw = _env(name).lower()
    if not raw:
        return default
    if raw in _TRUE:
        return True
    if raw in _FALSE:
        return False
    raise ConfigError(f"{name} は真偽値で指定してください: {raw!r}")


@dataclass(frozen=True, slots=True)
class Settings:
    """ゲートウェイ全体の設定。"""

    host: str
    port: int
    control_port: int

    upstream_base_url: str
    upstream_api_key: str
    control_token: str

    db_path: str
    path_policy: str
    global_concurrency: int
    global_queue_timeout: float
    filter_models_response: bool
    log_retention_days: int

    max_request_body_bytes: int
    max_grammar_bytes: int
    max_n_probs: int
    max_request_duration: float
    idle_timeout: float
    connect_timeout: float
    graceful_timeout: float

    key_cache_ttl: float
    rate_snapshot_interval: float
    models_cache_ttl: float
    key_pepper: str

    version: str = __version__

    @property
    def allowlist_mode(self) -> bool:
        return self.path_policy == PATH_POLICY_ALLOWLIST


def load_settings(*, require_secrets: bool = True) -> Settings:
    """環境変数から設定を組み立てる。

    ``require_secrets`` が真の場合、上流 URL と control トークンの欠落を起動時エラーとする。
    試験では偽を指定してダミー値で組み立てる。
    """

    upstream = _env("UPSTREAM_BASE_URL").rstrip("/")
    control_token = _env("CONTROL_TOKEN")
    policy = _env("PATH_POLICY", PATH_POLICY_DENYLIST).lower() or PATH_POLICY_DENYLIST

    if policy not in (PATH_POLICY_DENYLIST, PATH_POLICY_ALLOWLIST):
        raise ConfigError(
            f"PATH_POLICY は {PATH_POLICY_DENYLIST} か {PATH_POLICY_ALLOWLIST} を指定してください: {policy!r}"
        )
    if require_secrets:
        if not upstream:
            raise ConfigError("UPSTREAM_BASE_URL は必須です")
        if not upstream.startswith(("http://", "https://")):
            raise ConfigError(f"UPSTREAM_BASE_URL は http(s) URL で指定してください: {upstream!r}")
        if not control_token:
            raise ConfigError("CONTROL_TOKEN は必須です")
        if len(control_token) < 16:
            raise ConfigError("CONTROL_TOKEN は 16 文字以上で指定してください")

    return Settings(
        host=_env("HOST", "0.0.0.0"),
        port=_env_int("PORT", 3000, minimum=1),
        control_port=_env_int("CONTROL_PORT", 3001, minimum=1),
        upstream_base_url=upstream or "http://127.0.0.1:8080",
        upstream_api_key=_env("UPSTREAM_API_KEY"),
        control_token=control_token,
        db_path=_env("DB_PATH", "/data/gateway.db"),
        path_policy=policy,
        global_concurrency=_env_int("GLOBAL_CONCURRENCY", 4, minimum=1),
        global_queue_timeout=_env_float("GLOBAL_QUEUE_TIMEOUT", 30.0, minimum=0.0),
        filter_models_response=_env_bool("FILTER_MODELS_RESPONSE", True),
        log_retention_days=_env_int("LOG_RETENTION_DAYS", 90, minimum=1),
        max_request_body_bytes=_env_int("MAX_REQUEST_BODY_BYTES", 33554432, minimum=1),
        max_grammar_bytes=_env_int("MAX_GRAMMAR_BYTES", 65536, minimum=1),
        max_n_probs=_env_int("MAX_N_PROBS", 20, minimum=0),
        max_request_duration=_env_float("MAX_REQUEST_DURATION", 1800.0, minimum=1.0),
        idle_timeout=_env_float("IDLE_TIMEOUT", 300.0, minimum=1.0),
        connect_timeout=_env_float("CONNECT_TIMEOUT", 10.0, minimum=1.0),
        graceful_timeout=_env_float("GRACEFUL_TIMEOUT", 30.0, minimum=0.0),
        key_cache_ttl=_env_float("KEY_CACHE_TTL", 60.0, minimum=1.0),
        rate_snapshot_interval=_env_float("RATE_SNAPSHOT_INTERVAL", 30.0, minimum=1.0),
        models_cache_ttl=_env_float("MODELS_CACHE_TTL", 60.0, minimum=1.0),
        key_pepper=_env("KEY_PEPPER"),
    )
