"""Tests for the frame extractor module."""

import os
import subprocess
import tempfile
import unittest
from unittest.mock import MagicMock, call, patch

from video_thumbnail_creator.extractor import (
    NUM_FRAMES,
    FRAME_HEIGHT,
    FRAME_WIDTH,
    extract_frames,
)
from video_thumbnail_creator.utils import get_video_duration


class TestGetVideoDuration(unittest.TestCase):
    """Tests for the get_video_duration utility."""

    def test_returns_float_on_valid_output(self):
        with patch("video_thumbnail_creator.utils.subprocess.run") as mock_run:
            mock_run.return_value = MagicMock(stdout="123.456\n", returncode=0)
            result = get_video_duration("/fake/video.mp4")
        self.assertAlmostEqual(result, 123.456)

    def test_raises_on_empty_output(self):
        with patch("video_thumbnail_creator.utils.subprocess.run") as mock_run:
            mock_run.return_value = MagicMock(stdout="", returncode=0)
            with self.assertRaises(ValueError):
                get_video_duration("/fake/video.mp4")

    def test_calls_ffprobe_with_correct_arguments(self):
        with patch("video_thumbnail_creator.utils.subprocess.run") as mock_run:
            mock_run.return_value = MagicMock(stdout="60.0\n", returncode=0)
            get_video_duration("/fake/video.mp4")
        args = mock_run.call_args[0][0]
        self.assertEqual(args[0], "ffprobe")
        self.assertIn("/fake/video.mp4", args)


class TestExtractFrames(unittest.TestCase):
    """Tests for extract_frames."""

    def _make_fake_frame(self, path: str) -> None:
        """Create a small valid JPEG file at path."""
        from PIL import Image
        img = Image.new("RGB", (FRAME_WIDTH, FRAME_HEIGHT), color=(128, 128, 128))
        img.save(path, "JPEG")

    @patch("video_thumbnail_creator.extractor.is_hdr_video", return_value=False)
    @patch("video_thumbnail_creator.extractor.subprocess.run")
    @patch("video_thumbnail_creator.extractor.get_video_duration")
    def test_extract_frames_returns_correct_count(self, mock_duration, mock_run, _mock_hdr):
        mock_duration.return_value = 100.0

        with tempfile.TemporaryDirectory() as tmpdir:
            # Pre-create fake frame files so the paths exist
            for i in range(NUM_FRAMES):
                frame_path = os.path.join(tmpdir, f"frame_{i:02d}.jpg")
                self._make_fake_frame(frame_path)

            mock_run.return_value = MagicMock(returncode=0)
            frames = extract_frames("/fake/video.mp4", tmpdir)

        self.assertEqual(len(frames), NUM_FRAMES)

    @patch("video_thumbnail_creator.extractor.is_hdr_video", return_value=False)
    @patch("video_thumbnail_creator.extractor.subprocess.run")
    @patch("video_thumbnail_creator.extractor.get_video_duration")
    def test_extract_frames_calls_ffmpeg_correct_times(self, mock_duration, mock_run, _mock_hdr):
        mock_duration.return_value = 60.0
        mock_run.return_value = MagicMock(returncode=0)

        with tempfile.TemporaryDirectory() as tmpdir:
            extract_frames("/fake/video.mp4", tmpdir)

        self.assertEqual(mock_run.call_count, NUM_FRAMES)

    @patch("video_thumbnail_creator.extractor.is_hdr_video", return_value=False)
    @patch("video_thumbnail_creator.extractor.subprocess.run")
    @patch("video_thumbnail_creator.extractor.get_video_duration")
    def test_extract_frames_timestamps_are_evenly_distributed(
        self, mock_duration, mock_run, _mock_hdr
    ):
        duration = 100.0
        mock_duration.return_value = duration
        mock_run.return_value = MagicMock(returncode=0)

        with tempfile.TemporaryDirectory() as tmpdir:
            extract_frames("/fake/video.mp4", tmpdir)

        step = duration / NUM_FRAMES
        expected_timestamps = [step * i + step / 2 for i in range(NUM_FRAMES)]

        actual_timestamps = []
        for c in mock_run.call_args_list:
            cmd = c[0][0]
            # -ss <timestamp> -i <path> ...
            ss_idx = cmd.index("-ss")
            actual_timestamps.append(float(cmd[ss_idx + 1]))

        for expected, actual in zip(expected_timestamps, actual_timestamps):
            self.assertAlmostEqual(expected, actual, places=6)

    @patch("video_thumbnail_creator.extractor.is_hdr_video", return_value=False)
    @patch("video_thumbnail_creator.extractor.subprocess.run")
    @patch("video_thumbnail_creator.extractor.get_video_duration")
    def test_extract_frames_uses_blurred_background_filter(
        self, mock_duration, mock_run, _mock_hdr
    ):
        mock_duration.return_value = 30.0
        mock_run.return_value = MagicMock(returncode=0)

        with tempfile.TemporaryDirectory() as tmpdir:
            extract_frames("/fake/video.mp4", tmpdir)

        # All ffmpeg calls should contain the blurred background filter
        for c in mock_run.call_args_list:
            cmd = c[0][0]
            fc_idx = cmd.index("-filter_complex")
            fc_value = cmd[fc_idx + 1]
            self.assertIn("boxblur", fc_value)
            self.assertIn("overlay", fc_value)


class TestMosaicCreation(unittest.TestCase):
    """Tests for the mosaic module."""

    def _make_frame_paths(self, tmpdir: str, count: int = 20) -> list:
        from PIL import Image
        paths = []
        for i in range(count):
            path = os.path.join(tmpdir, f"frame_{i:02d}.jpg")
            img = Image.new("RGB", (FRAME_WIDTH, FRAME_HEIGHT), color=(i * 10 % 255, 0, 0))
            img.save(path, "JPEG")
            paths.append(path)
        return paths

    def test_create_mosaic_produces_file(self):
        from video_thumbnail_creator.mosaic import create_mosaic, MOSAIC_WIDTH, MOSAIC_HEIGHT

        with tempfile.TemporaryDirectory() as tmpdir:
            frame_paths = self._make_frame_paths(tmpdir)
            mosaic_out = os.path.join(tmpdir, "mosaic.jpg")
            result = create_mosaic(frame_paths, mosaic_out)
            self.assertTrue(os.path.isfile(result))

    def test_create_mosaic_correct_dimensions(self):
        from PIL import Image
        from video_thumbnail_creator.mosaic import create_mosaic, MOSAIC_WIDTH, MOSAIC_HEIGHT

        with tempfile.TemporaryDirectory() as tmpdir:
            frame_paths = self._make_frame_paths(tmpdir)
            mosaic_out = os.path.join(tmpdir, "mosaic.jpg")
            create_mosaic(frame_paths, mosaic_out)

            with Image.open(mosaic_out) as img:
                self.assertEqual(img.size, (MOSAIC_WIDTH, MOSAIC_HEIGHT))

    def test_create_mosaic_raises_on_wrong_frame_count(self):
        from video_thumbnail_creator.mosaic import create_mosaic

        with tempfile.TemporaryDirectory() as tmpdir:
            frame_paths = self._make_frame_paths(tmpdir, count=5)
            mosaic_out = os.path.join(tmpdir, "mosaic.jpg")
            with self.assertRaises(ValueError):
                create_mosaic(frame_paths, mosaic_out)


if __name__ == "__main__":
    unittest.main()
