import tempfile
import unittest
from pathlib import Path
from unittest.mock import MagicMock

from watchfiles import Change

from app.monitor import DirectoryChangeEvent, LocalDirectoryWatcher, Monitor


class CallbackRecorder:
    """
    测试用目录监控回调记录器。
    """

    def __init__(self):
        """
        初始化事件记录列表。
        """
        self.events = []

    def event_handler(self, event, text: str, event_path: str, file_size: int = None):
        """
        记录目录监控分发出来的事件。
        :param event: 目录监控事件
        :param text: 事件描述
        :param event_path: 事件路径
        :param file_size: 文件大小
        """
        self.events.append((event, text, event_path, file_size))


class LocalDirectoryWatcherTest(unittest.TestCase):
    """
    watchfiles 本地目录监控测试。
    """

    def test_handle_changes_dispatches_added_and_modified_files(self):
        """
        新增和修改文件应转换成目录监控整理回调。
        """
        with tempfile.TemporaryDirectory() as temp_dir:
            watch_dir = Path(temp_dir)
            added_file = watch_dir / "a_added.mkv"
            modified_file = watch_dir / "b_modified.mkv"
            skipped_dir = watch_dir / "c_dir"
            added_file.write_bytes(b"added")
            modified_file.write_bytes(b"modified")
            skipped_dir.mkdir()

            callback = CallbackRecorder()
            watcher = LocalDirectoryWatcher(watch_dir, callback=callback, force_polling=True)
            watcher._handle_changes({
                (Change.added, added_file.as_posix()),
                (Change.modified, modified_file.as_posix()),
                (Change.deleted, added_file.as_posix()),
                (Change.added, skipped_dir.as_posix()),
            })

            self.assertEqual(2, len(callback.events))
            self.assertEqual((Change.added, "新增", added_file.as_posix(), 5),
                             (callback.events[0][0].change_type,
                              callback.events[0][1],
                              callback.events[0][2],
                              callback.events[0][3]))
            self.assertEqual((Change.modified, "修改", modified_file.as_posix(), 8),
                             (callback.events[1][0].change_type,
                              callback.events[1][1],
                              callback.events[1][2],
                              callback.events[1][3]))

    def test_handle_changes_skips_missing_paths(self):
        """
        事件到达时已经消失的路径不应触发整理。
        """
        with tempfile.TemporaryDirectory() as temp_dir:
            watch_dir = Path(temp_dir)
            missing_file = watch_dir / "missing.mkv"

            callback = CallbackRecorder()
            watcher = LocalDirectoryWatcher(watch_dir, callback=callback, force_polling=True)
            watcher._handle_changes({(Change.added, missing_file.as_posix())})

            self.assertEqual([], callback.events)


class MonitorWatchfilesEventTest(unittest.TestCase):
    """
    Monitor 对 watchfiles 事件的兼容处理测试。
    """

    def test_event_handler_routes_file_events_to_transfer_handler(self):
        """
        文件事件应继续按 local 存储交给整理流程。
        """
        monitor = object.__new__(Monitor)
        monitor.all_exts = [".mkv"]
        handle_file = MagicMock()
        setattr(monitor, "_Monitor__handle_file", handle_file)
        event_path = Path("/downloads/movie.mkv")
        event = DirectoryChangeEvent(
            change_type=Change.added,
            src_path=event_path.as_posix(),
            is_directory=False
        )

        monitor.event_handler(
            event=event,
            text="新增",
            event_path=event_path.as_posix(),
            file_size=1024
        )

        handle_file.assert_called_once_with(
            storage="local",
            event_path=event_path,
            file_size=1024
        )

    def test_event_handler_ignores_directory_events(self):
        """
        目录事件不应进入文件整理流程。
        """
        monitor = object.__new__(Monitor)
        monitor.all_exts = [".mkv"]
        handle_file = MagicMock()
        setattr(monitor, "_Monitor__handle_file", handle_file)
        event_path = Path("/downloads/folder")
        event = DirectoryChangeEvent(
            change_type=Change.added,
            src_path=event_path.as_posix(),
            is_directory=True
        )

        monitor.event_handler(
            event=event,
            text="新增",
            event_path=event_path.as_posix()
        )

        handle_file.assert_not_called()

    def test_event_handler_ignores_download_temp_files(self):
        """
        下载器临时文件不应进入整理流程。
        """
        monitor = object.__new__(Monitor)
        monitor.all_exts = [".mkv"]
        handle_file = MagicMock()
        setattr(monitor, "_Monitor__handle_file", handle_file)
        event_path = Path("/downloads/movie.mkv.!qB")
        event = DirectoryChangeEvent(
            change_type=Change.modified,
            src_path=event_path.as_posix(),
            is_directory=False
        )

        monitor.event_handler(
            event=event,
            text="修改",
            event_path=event_path.as_posix(),
            file_size=1024
        )

        handle_file.assert_not_called()

    def test_event_handler_ignores_non_transferable_files(self):
        """
        非可整理后缀文件不应进入整理流程。
        """
        monitor = object.__new__(Monitor)
        monitor.all_exts = [".mkv"]
        handle_file = MagicMock()
        setattr(monitor, "_Monitor__handle_file", handle_file)
        event_path = Path("/downloads/movie.nfo")
        event = DirectoryChangeEvent(
            change_type=Change.added,
            src_path=event_path.as_posix(),
            is_directory=False
        )

        monitor.event_handler(
            event=event,
            text="新增",
            event_path=event_path.as_posix(),
            file_size=1024
        )

        handle_file.assert_not_called()
