"""调试截图资源回收器：每小时清理，100MB 上限。

清理 /tmp/woc_debug/ 下的调试截图：
- 按 mtime 删除超过 max_age_hours 的文件
- 总量超 max_total_mb 时按 LRU（最旧 mtime）删除最老文件直到达标
"""

from __future__ import annotations

import asyncio
import logging
import os
import time
from typing import Optional

logger = logging.getLogger("woc-bridge")


class ResourceReaper:
    """调试截图资源回收器。

    Attributes:
        debug_screenshot_dir: 调试截图目录，默认 /tmp/woc_debug
        max_age_hours: 文件最大存活小时数，默认 24
        max_total_mb: 目录总大小上限 MB，默认 100
        interval: 检查间隔秒数，默认 3600（1 小时）
    """

    def __init__(
        self,
        debug_screenshot_dir: str = "/tmp/woc_debug",
        max_age_hours: int = 24,
        max_total_mb: int = 100,
        interval: float = 3600.0,
    ) -> None:
        self.debug_screenshot_dir = debug_screenshot_dir
        self.max_age_hours = max_age_hours
        self.max_total_mb = max_total_mb
        self.interval = interval
        self._task: Optional[asyncio.Task] = None
        self._stopped = False

    async def start(self) -> None:
        """启动后台回收任务。"""
        if self._task is not None and not self._task.done():
            return
        self._stopped = False
        self._task = asyncio.create_task(self._run())
        logger.info(
            "[reaper] started (dir=%s max_age=%dh max_total=%dMB)",
            self.debug_screenshot_dir, self.max_age_hours, self.max_total_mb,
        )

    async def stop(self) -> None:
        """停止回收任务。"""
        self._stopped = True
        if self._task is not None and not self._task.done():
            self._task.cancel()
            try:
                await asyncio.wait_for(self._task, timeout=2.0)
            except (asyncio.TimeoutError, asyncio.CancelledError):
                pass
        self._task = None
        logger.info("[reaper] stopped")

    async def _run(self) -> None:
        """主循环：每 interval 秒清理一次。"""
        while not self._stopped:
            try:
                await asyncio.sleep(self.interval)
                await self.cleanup()
            except asyncio.CancelledError:
                break
            except Exception as exc:
                logger.warning("[reaper] cleanup exception: %s", exc)

    async def cleanup(self) -> None:
        """执行一次清理：TTL 删除 + 总量限制 LRU 删除。"""
        await asyncio.to_thread(self._cleanup_sync)

    def _cleanup_sync(self) -> None:
        """同步清理实现（在 to_thread 中执行）。"""
        if not os.path.isdir(self.debug_screenshot_dir):
            return

        now = time.time()
        max_age_sec = self.max_age_hours * 3600

        # 1. 收集所有文件 + mtime + size
        files: list[tuple[str, float, int]] = []  # (path, mtime, size)
        for name in os.listdir(self.debug_screenshot_dir):
            path = os.path.join(self.debug_screenshot_dir, name)
            if not os.path.isfile(path):
                continue
            try:
                stat = os.stat(path)
                files.append((path, stat.st_mtime, stat.st_size))
            except OSError:
                continue

        if not files:
            return

        # 2. 删除超 TTL 的文件
        deleted_ttl = 0
        survivors: list[tuple[str, float, int]] = []
        for path, mtime, size in files:
            if now - mtime > max_age_sec:
                try:
                    os.remove(path)
                    deleted_ttl += 1
                except OSError as exc:
                    logger.debug("[reaper] remove %s failed: %s", path, exc)
                    survivors.append((path, mtime, size))
            else:
                survivors.append((path, mtime, size))

        if deleted_ttl > 0:
            logger.info(
                "[reaper] deleted %d files (TTL > %dh)", deleted_ttl, self.max_age_hours
            )

        # 3. 总量超限时按 LRU（最旧 mtime）删除
        total_bytes = sum(s for _, _, s in survivors)
        max_bytes = self.max_total_mb * 1024 * 1024
        if total_bytes <= max_bytes:
            return

        # 按 mtime 升序（最旧在前）
        survivors.sort(key=lambda x: x[1])
        deleted_lru = 0
        for path, _, size in survivors:
            if total_bytes <= max_bytes:
                break
            try:
                os.remove(path)
                total_bytes -= size
                deleted_lru += 1
            except OSError as exc:
                logger.debug("[reaper] remove %s failed: %s", path, exc)

        if deleted_lru > 0:
            logger.info(
                "[reaper] deleted %d files (LRU, total now %.1fMB)",
                deleted_lru, total_bytes / (1024 * 1024),
            )
