"""通过经纬度反查城市（reverse_geocoder 离线库，零网络调用）。

reverse_geocoder 内置 ~2.5M 条全球城市/聚居点的经纬度→地名映射表，
构建一次 KDTree（~几十MB 内存）后，查询为纯内存搜索，不作任何外部网络调用。

⚠️ 必须持有单例、且用 mode=1（单进程）：
  - reverse_geocoder 的模块级 rg.search()/rg.get() **每次调用都会 new 一个 RGeocoder**，
    即每次都重新解析 ~2.5M 行 CSV + 重建 KDTree（数秒/次）。绝不能在服务端按请求调用。
  - 默认 mode=2 用 multiprocessing 按 CPU 数 spawn 子进程做并行查询；在服务端 / Windows
    spawn 下会重复 import 主模块（无 __main__ guard 时直接报错），既慢又危险。
  故本模块持有一个 mode=1 的 RGeocoder 单例，建一次树、复用；查询走单进程内存搜索。

⚠️ 精度说明：gazetteer 里的中国数据粒度不一致——直辖市/省会通常直接命中城市名，
但部分城市会命中到区/街道级（如天津→Erwangzhuang、西安→Zhangjiabao），
此时 admin1（省级行政区）可作为回退。业务侧建议优先用 admin1 做城市级判定。
"""
from __future__ import annotations

from typing import Any

import reverse_geocoder as rg  # type: ignore[import-untyped]

# mode=1 单进程 KDTree 的单例;None 表示尚未构建(见 ensure_loaded)。
_geocoder: rg.RGeocoder | None = None


def ensure_loaded() -> None:
    """构建（或复用）RGeocoder 单例（幂等）。

    首次调用解析 ~2.5M 行 CSV + 构建 KDTree（~秒级、数十 MB）。生产应在 main.py 的
    lifespan 启动阶段主动调用一次，把这份一次性成本摊到启动，避免砸在第一个
    /feed?tab=rec / /top-sales 请求上。mode=1 = 单进程，不 spawn 子进程。
    """
    global _geocoder
    if _geocoder is None:
        _geocoder = rg.RGeocoder(mode=1, verbose=False)


def get_city(latitude: float, longitude: float) -> dict[str, str]:
    """根据经纬度反查最近聚居点。

    返回 dict:
      - name:       最近聚居点名称（英文），如 "Beijing" / "Fengsheng"；
                    中国境内可能是区/街道级；海洋/无人区返 ""
      - admin1:     省级行政区（英文），如 "Beijing" / "Hubei" / "Chongqing Shi"；
                    直辖市 admin1 即为城市名
      - country:    ISO 3166-1 alpha-2，如 "CN"
      - latitude:   匹配到的参考点纬度（字符串）
      - longitude:  匹配到的参考点经度（字符串）

    未匹配到（海洋/远洋）时返回空字符串字段。
    """
    ensure_loaded()
    assert _geocoder is not None  # ensure_loaded 保证已构建
    results: list[dict[str, Any]] = _geocoder.query([(latitude, longitude)])
    if not results:
        return {"name": "", "admin1": "", "country": "", "latitude": "", "longitude": ""}
    r = results[0]
    return {
        "name": str(r.get("name", "")),
        "admin1": str(r.get("admin1", "")),
        "country": str(r.get("cc", "")),
        "latitude": str(r.get("lat", "")),
        "longitude": str(r.get("lon", "")),
    }
