"""SafeRotatingFileHandler:Windows 轮转不被外部句柄卡死(WinError 32)。

stdlib RotatingFileHandler 靠 rename 活动文件轮转;Windows 上只要有第二个句柄(admin
第二进程、残留 --reload worker、IDE 索引、杀软)开着它, rename 就 WinError 32、轮转永久
卡死。Safe 版在 Windows 改走 copytruncate(拷贝→就地清空, 从不 rename 活动文件)。

这些用例用 monkeypatch 把 os.name 强制成 "nt", 使 copytruncate 分支在任何 OS 的 CI 上都被
覆盖;在真实 Windows 上则天然命中。
"""
from __future__ import annotations

import logging
from pathlib import Path

from app.core.logging import SafeRotatingFileHandler


def _emit(handler: logging.Handler, msg: str) -> None:
    handler.emit(logging.LogRecord("t", logging.INFO, __file__, 0, msg, (), None))


def test_rollover_survives_second_open_handle(tmp_path: Path, monkeypatch) -> None:
    """第二个句柄开着活动文件时轮转:不抛异常, 且确实转了(生成 .1、活动文件就地清空)。"""
    monkeypatch.setattr("app.core.logging.os.name", "nt")

    log_file = tmp_path / "app-server.log"
    # maxBytes 放大, 避免 emit 期间自动轮转干扰;本用例手动触发 doRollover。
    handler = SafeRotatingFileHandler(
        str(log_file), maxBytes=10**9, backupCount=3, encoding="utf-8",
    )
    handler.setFormatter(logging.Formatter("%(message)s"))
    try:
        for i in range(20):
            _emit(handler, f"line-{i:03d}")
        handler.flush()
        before = log_file.stat().st_size
        assert before > 0

        # 正是 Windows 上 rename 失败的条件:另一个句柄开着活动文件。
        with open(log_file, "a", encoding="utf-8"):
            handler.doRollover()  # 不应抛 PermissionError / WinError 32

        backup = tmp_path / "app-server.log.1"
        assert backup.exists()
        assert backup.stat().st_size == before   # 轮转前内容完整进了备份
        assert log_file.stat().st_size == 0      # 活动文件就地清空(不是 rename)

        # 句柄没被 rename 破坏, 仍能继续写。
        _emit(handler, "after-rollover")
        handler.flush()
        assert log_file.stat().st_size > 0
    finally:
        handler.close()


def test_backups_shift_and_capped(tmp_path: Path, monkeypatch) -> None:
    """多次轮转:.1/.2 逐级腾挪, 超过 backupCount 的丢弃(不出现 .3)。"""
    monkeypatch.setattr("app.core.logging.os.name", "nt")

    log_file = tmp_path / "app-server.log"
    handler = SafeRotatingFileHandler(
        str(log_file), maxBytes=10**9, backupCount=2, encoding="utf-8",
    )
    handler.setFormatter(logging.Formatter("%(message)s"))
    try:
        for _ in range(4):
            _emit(handler, "x" * 50)
            handler.doRollover()
        assert (tmp_path / "app-server.log.1").exists()
        assert (tmp_path / "app-server.log.2").exists()
        assert not (tmp_path / "app-server.log.3").exists()
    finally:
        handler.close()
