"""0 点自动兑金币(每日定时:把每个用户金币「到分全额」兑成现金)。

背景:客户端已删手动「兑金币」入口、改文案「0 点自动兑现金（可能存在延迟）」,故服务端需在
每天 0 点把金币按汇率(100 金币 = 1 分)兑成现金。复用 exchange_in 流水类型(手动兑入口已下线,
不会与之混淆),幂等键 = 用户当天是否已有 exchange_in 流水。核心逻辑见
app/repositories/wallet.py::daily_auto_exchange。

用法:
    # 单轮(给 systemd timer / cron 用,线上每天 0 点一次)
    python -m scripts.daily_auto_exchange --once

    # 本地循环(调试用,默认每小时一轮——真到分兑换幂等,空跑无害)
    python -m scripts.daily_auto_exchange --loop --interval 3600

开关:.env 的 AUTO_EXCHANGE_ENABLED=false 时脚本直接 no-op 退出(免动 systemd timer)。
带文件锁,防上一轮没跑完下一轮又起。
"""
from __future__ import annotations

import argparse
import os
import sys
import tempfile
import time
from datetime import datetime

# Windows 控制台按 UTF-8 输出中文/¥
try:
    sys.stdout.reconfigure(encoding="utf-8")  # type: ignore[attr-defined]
except Exception:  # noqa: BLE001
    pass

from app.core.config import settings
from app.db.session import SessionLocal
from app.repositories import wallet as wallet_repo

# 锁放系统临时目录(任何账号可写),不依赖代码目录 data/ 的写权限。需要指定位置时用环境变量覆盖。
LOCK_FILE = os.environ.get("DAILY_EXCHANGE_LOCK") or os.path.join(
    tempfile.gettempdir(), "daily_auto_exchange.lock"
)
LOCK_STALE_SEC = 30 * 60   # 锁超过 30min 视为陈旧(进程异常退出残留),自动接管


def _acquire_lock() -> bool:
    os.makedirs(os.path.dirname(LOCK_FILE) or ".", exist_ok=True)
    try:
        fd = os.open(LOCK_FILE, os.O_CREAT | os.O_EXCL | os.O_WRONLY)
        os.write(fd, f"{os.getpid()} {time.time()}".encode())
        os.close(fd)
        return True
    except FileExistsError:
        try:
            with open(LOCK_FILE) as f:
                parts = f.read().split()
            ts = float(parts[1]) if len(parts) > 1 else 0.0
            if time.time() - ts > LOCK_STALE_SEC:
                os.remove(LOCK_FILE)
                return _acquire_lock()
        except OSError:
            pass
        return False


def _release_lock() -> None:
    try:
        os.remove(LOCK_FILE)
    except OSError:
        pass


def run_once() -> None:
    if not settings.AUTO_EXCHANGE_ENABLED:
        print(f"[{datetime.now():%H:%M:%S}] AUTO_EXCHANGE_ENABLED=false,跳过(no-op)")
        return
    if not _acquire_lock():
        print(f"[{datetime.now():%H:%M:%S}] 上一轮还在跑(锁占用),跳过本轮")
        return
    t0 = time.time()
    try:
        with SessionLocal() as db:
            stats = wallet_repo.daily_auto_exchange(db)
        dt = time.time() - t0
        print(
            f"[{datetime.now():%H:%M:%S}] 自动兑完成 用时{dt:.1f}s "
            f"扫描{stats['scanned']} 兑换{stats['converted']} "
            f"已兑过跳过{stats['skipped_done']} 零头跳过{stats['skipped_dust']} "
            f"失败{stats['failed']} 累计兑入{stats['total_cents']}分"
        )
    finally:
        _release_lock()


def main() -> None:
    ap = argparse.ArgumentParser(description="0 点自动兑金币")
    ap.add_argument("--once", action="store_true", help="只跑一轮(默认)")
    ap.add_argument("--loop", action="store_true", help="循环跑(本地调试)")
    ap.add_argument("--interval", type=int, default=3600, help="循环间隔秒(默认 3600=1h)")
    args = ap.parse_args()

    if args.loop:
        print(f"循环模式,每 {args.interval}s 一轮(Ctrl+C 退出)")
        try:
            while True:
                run_once()
                time.sleep(args.interval)
        except KeyboardInterrupt:
            print("已退出")
    else:
        run_once()


if __name__ == "__main__":
    main()
