from __future__ import annotations

from functools import lru_cache
from pathlib import Path
from typing import Literal, Optional

from pydantic import ValidationError
from pydantic_settings import BaseSettings, SettingsConfigDict


def _guess_env_file(app_env: str) -> Optional[str]:
    """
    根据 APP_ENV 选择 env 文件。

    说明：
    - `.env.dev` / `.env.prod` 不进入仓库，通常由运维在部署时放到 `server/` 目录
    - 如果文件不存在，则返回 None，让系统从“真实环境变量”读取
    """

    base_dir = Path(__file__).resolve().parents[2]  # .../server/app -> .../server
    candidates = {
        "dev": base_dir / ".env.dev",
        "prod": base_dir / ".env.prod",
    }
    p = candidates.get(app_env)
    if p and p.exists():
        return str(p)
    return None


class Settings(BaseSettings):
    """
    应用配置（统一从环境变量读取）。

    注意：请不要把真实账号密码写入仓库；使用 `.env.dev/.env.prod` 或部署系统注入。
    """

    app_env: Literal["dev", "prod"] = "dev"
    app_name: str = "mindfulness-server"
    app_host: str = "0.0.0.0"
    app_port: int = 8000

    database_url: str

    redis_url: str
    celery_broker_url: str
    celery_result_backend: Optional[str] = None

    # 法务协议链接（由后端统一托管并下发给客户端）
    # 说明：
    # - 当前仅支持 EN / TC 两种语言（与客户端现状一致）
    # - 若未显式配置 LEGAL_*，后端会使用“内置协议页面”（/v1/legal/privacy、/v1/legal/terms）
    # - 线上建议配置为你们的官网/托管页/静态站点 HTTPS 链接，避免与 API 域名强绑定
    #
    # 这里用一个内部哨兵值表示“走内置页面”，避免默认值误导为可用的公网链接。
    legal_privacy_url_en: str = "__internal__"
    legal_terms_url_en: str = "__internal__"
    legal_privacy_url_tc: Optional[str] = None
    legal_terms_url_tc: Optional[str] = None

    # Expo Push（可选）
    # 说明：
    # - 不填也能调用 Expo Push API，但会受更严格的速率限制
    # - 建议生产环境配置 EXPO_ACCESS_TOKEN，便于稳定性与配额
    expo_access_token: Optional[str] = None

    model_config = SettingsConfigDict(
        env_prefix="",
        case_sensitive=False,
        extra="ignore",
    )


@lru_cache
def get_settings() -> Settings:
    """
    获取配置（带缓存）。

    加载顺序：
    - 优先使用 `.env.dev/.env.prod`（若存在）
    - 否则使用系统环境变量
    """

    import os

    env = os.getenv("APP_ENV", "dev").strip() or "dev"
    env_file = _guess_env_file(env)

    try:
        return Settings(_env_file=env_file)
    except ValidationError as e:
        # 给出更可执行的错误信息，避免只看到一串校验栈。
        missing_fields: list[str] = []
        for err in e.errors():
            if err.get("type") == "missing":
                loc = err.get("loc") or ()
                if loc:
                    missing_fields.append(str(loc[0]))

        # 将字段名映射为常见的环境变量名（默认规则：字段名大写）
        missing_env_keys = [f.upper() for f in missing_fields] if missing_fields else []

        env_file_hint = env_file or f".env.{env}（未找到，已回退到系统环境变量）"
        required_hint = (
            "、".join(missing_env_keys)
            if missing_env_keys
            else "DATABASE_URL、REDIS_URL、CELERY_BROKER_URL"
        )

        msg = (
            "应用启动失败：缺少必填配置。\n\n"
            f"- 当前 APP_ENV：{env}\n"
            f"- 期望读取的 env 文件：{env_file_hint}\n"
            f"- 缺少的环境变量：{required_hint}\n\n"
            "修复方式（任选其一）：\n"
            "1) 直接注入环境变量（推荐）：\n"
            "   - DATABASE_URL=...\n"
            "   - REDIS_URL=...\n"
            "   - CELERY_BROKER_URL=...\n"
            "2) 使用 env 文件：在 `server/` 下准备 `.env.dev` 或 `.env.prod`（KEY=value 格式），\n"
            "   本地可通过 `server/run.sh --env dev|prod` 自动加载；Docker 运行可用 `--env-file` 传入。\n"
        )
        # 不附带原始 ValidationError 的异常上下文，减少日志噪音；
        # msg 已包含缺失项与修复方式，足够定位问题。
        raise RuntimeError(msg) from None

