"""Tests for the embedded_image module and related CLI behaviour."""

import argparse
import io
import json
import os
import tempfile
import unittest
from contextlib import redirect_stdout
from unittest.mock import MagicMock, patch

from video_thumbnail_creator.embedded_image import (
    detect_embedded_image,
    extract_embedded_image,
)


# ---------------------------------------------------------------------------
# detect_embedded_image tests
# ---------------------------------------------------------------------------

class TestDetectEmbeddedImageFound(unittest.TestCase):
    """detect_embedded_image returns True when attached_pic stream exists."""

    def _ffprobe_output(self):
        return json.dumps({
            "streams": [
                {
                    "codec_name": "h264",
                    "disposition": {"attached_pic": 0, "default": 1},
                },
                {
                    "codec_name": "mjpeg",
                    "disposition": {"attached_pic": 1, "default": 0},
                },
            ]
        })

    def test_returns_true(self):
        mock_result = MagicMock(returncode=0, stdout=self._ffprobe_output())
        with patch("video_thumbnail_creator.embedded_image.subprocess.run", return_value=mock_result):
            self.assertTrue(detect_embedded_image("/fake/video.mp4"))

    def test_ffprobe_called_with_video_path(self):
        mock_result = MagicMock(returncode=0, stdout=self._ffprobe_output())
        with patch("video_thumbnail_creator.embedded_image.subprocess.run", return_value=mock_result) as mock_run:
            detect_embedded_image("/fake/video.mp4")
        cmd = mock_run.call_args[0][0]
        self.assertIn("/fake/video.mp4", cmd)
        self.assertIn("ffprobe", cmd[0])


class TestDetectEmbeddedImageNotFound(unittest.TestCase):
    """detect_embedded_image returns False when no attached_pic stream exists."""

    def test_returns_false_when_no_attached_pic(self):
        output = json.dumps({
            "streams": [
                {
                    "codec_name": "h264",
                    "disposition": {"attached_pic": 0, "default": 1},
                },
            ]
        })
        mock_result = MagicMock(returncode=0, stdout=output)
        with patch("video_thumbnail_creator.embedded_image.subprocess.run", return_value=mock_result):
            self.assertFalse(detect_embedded_image("/fake/video.mp4"))

    def test_returns_false_when_no_streams(self):
        output = json.dumps({"streams": []})
        mock_result = MagicMock(returncode=0, stdout=output)
        with patch("video_thumbnail_creator.embedded_image.subprocess.run", return_value=mock_result):
            self.assertFalse(detect_embedded_image("/fake/video.mp4"))

    def test_returns_false_on_ffprobe_error(self):
        mock_result = MagicMock(returncode=1, stdout="")
        with patch("video_thumbnail_creator.embedded_image.subprocess.run", return_value=mock_result):
            self.assertFalse(detect_embedded_image("/fake/video.mp4"))

    def test_returns_false_on_invalid_json(self):
        mock_result = MagicMock(returncode=0, stdout="not json")
        with patch("video_thumbnail_creator.embedded_image.subprocess.run", return_value=mock_result):
            self.assertFalse(detect_embedded_image("/fake/video.mp4"))

    def test_uses_custom_ffprobe_binary(self):
        output = json.dumps({"streams": []})
        mock_result = MagicMock(returncode=0, stdout=output)
        with patch("video_thumbnail_creator.embedded_image.subprocess.run", return_value=mock_result) as mock_run:
            detect_embedded_image("/fake/video.mp4", ffprobe="/custom/ffprobe")
        # call_args_list[0] is the ffprobe call; [1] would be AtomicParsley fallback
        cmd = mock_run.call_args_list[0][0][0]
        self.assertEqual(cmd[0], "/custom/ffprobe")


# ---------------------------------------------------------------------------
# extract_embedded_image tests
# ---------------------------------------------------------------------------

class TestExtractEmbeddedImage(unittest.TestCase):
    """Tests for extract_embedded_image."""

    def _make_probe_output(self, stream_index: int = 1) -> str:
        return json.dumps({
            "streams": [
                {
                    "index": 0,
                    "codec_name": "h264",
                    "disposition": {"attached_pic": 0},
                },
                {
                    "index": stream_index,
                    "codec_name": "mjpeg",
                    "disposition": {"attached_pic": 1},
                },
            ]
        })

    def test_extracts_image_and_returns_abs_path(self):
        probe_result = MagicMock(returncode=0, stdout=self._make_probe_output())
        ffmpeg_result = MagicMock(returncode=0)
        with patch(
            "video_thumbnail_creator.embedded_image.subprocess.run",
            side_effect=[probe_result, ffmpeg_result],
        ):
            with tempfile.TemporaryDirectory() as tmpdir:
                out = os.path.join(tmpdir, "cover.jpg")
                result = extract_embedded_image("/fake/video.mp4", out)
            self.assertTrue(os.path.isabs(result))

    def test_raises_when_no_embedded_image(self):
        probe_result = MagicMock(
            returncode=0,
            stdout=json.dumps({"streams": [{"index": 0, "codec_name": "h264", "disposition": {"attached_pic": 0}}]}),
        )
        # Simulate AtomicParsley not installed so the fallback raises RuntimeError
        with patch(
            "video_thumbnail_creator.embedded_image.subprocess.run",
            side_effect=[probe_result, FileNotFoundError("AtomicParsley not found")],
        ):
            with self.assertRaises(RuntimeError):
                extract_embedded_image("/fake/video.mp4", "/tmp/out.jpg")

    def test_raises_on_ffprobe_failure(self):
        probe_result = MagicMock(returncode=1, stdout="", stderr="error")
        with patch("video_thumbnail_creator.embedded_image.subprocess.run", return_value=probe_result):
            with self.assertRaises(RuntimeError):
                extract_embedded_image("/fake/video.mp4", "/tmp/out.jpg")

    def test_ffmpeg_called_with_correct_stream_map(self):
        probe_result = MagicMock(returncode=0, stdout=self._make_probe_output(stream_index=2))
        ffmpeg_result = MagicMock(returncode=0)
        with patch(
            "video_thumbnail_creator.embedded_image.subprocess.run",
            side_effect=[probe_result, ffmpeg_result],
        ) as mock_run:
            extract_embedded_image("/fake/video.mp4", "/tmp/out.jpg")
        # Second call is ffmpeg
        ffmpeg_cmd = mock_run.call_args_list[1][0][0]
        self.assertIn("-map", ffmpeg_cmd)
        map_idx = ffmpeg_cmd.index("-map")
        self.assertEqual(ffmpeg_cmd[map_idx + 1], "0:2")


# ---------------------------------------------------------------------------
# CLI argument parsing tests
# ---------------------------------------------------------------------------

class TestEmbeddedImageCLIArgument(unittest.TestCase):
    """Test that --embedded-image is parsed correctly."""

    def _build_parser(self):
        from video_thumbnail_creator.cli import _build_parser
        return _build_parser()

    def test_prefer_choice(self):
        parser = self._build_parser()
        args = parser.parse_args(["extract", "video.mp4", "--embedded-image", "prefer"])
        self.assertEqual(args.embedded_image, "prefer")

    def test_ignore_choice(self):
        parser = self._build_parser()
        args = parser.parse_args(["extract", "video.mp4", "--embedded-image", "ignore"])
        self.assertEqual(args.embedded_image, "ignore")

    def test_ask_choice(self):
        parser = self._build_parser()
        args = parser.parse_args(["extract", "video.mp4", "--embedded-image", "ask"])
        self.assertEqual(args.embedded_image, "ask")

    def test_default_is_none(self):
        parser = self._build_parser()
        args = parser.parse_args(["extract", "video.mp4"])
        self.assertIsNone(args.embedded_image)

    def test_invalid_choice_raises(self):
        parser = self._build_parser()
        with self.assertRaises(SystemExit):
            parser.parse_args(["extract", "video.mp4", "--embedded-image", "always"])


# ---------------------------------------------------------------------------
# Config key tests
# ---------------------------------------------------------------------------

class TestEmbeddedImageConfig(unittest.TestCase):
    """Test that defaults.embedded_image is in ALLOWED_KEYS with correct default."""

    def test_key_in_allowed_keys(self):
        from video_thumbnail_creator.config import ALLOWED_KEYS
        self.assertIn("defaults.embedded_image", ALLOWED_KEYS)

    def test_default_value_is_ask(self):
        from video_thumbnail_creator.config import _BUILTIN_DEFAULTS
        self.assertEqual(_BUILTIN_DEFAULTS["defaults"]["embedded_image"], "ask")

    def test_effective_config_includes_embedded_image(self):
        from video_thumbnail_creator.config import get_effective_config
        with patch("video_thumbnail_creator.config.load_config", return_value={}):
            result = get_effective_config()
        self.assertEqual(result["defaults"]["embedded_image"], "ask")


# ---------------------------------------------------------------------------
# JSON output 'source' field tests
# ---------------------------------------------------------------------------

class TestEmitResultSource(unittest.TestCase):
    """Tests for the source field in JSON output."""

    def _emit(self, **kwargs):
        from video_thumbnail_creator.cli import _emit_result
        buf = io.StringIO()
        with redirect_stdout(buf):
            _emit_result(**kwargs)
        return json.loads(buf.getvalue())

    def test_source_frame_in_json(self):
        data = self._emit(
            poster_path="/tmp/p.jpg",
            frame_index=5,
            mode="auto",
            reasoning="ok",
            input_path="/tmp/v.mp4",
            use_json=True,
            source="frame",
        )
        self.assertEqual(data["source"], "frame")

    def test_source_embedded_in_json(self):
        data = self._emit(
            poster_path="/tmp/p.jpg",
            frame_index=-1,
            mode="auto",
            reasoning="Embedded cover art was used as source image.",
            input_path="/tmp/v.mp4",
            use_json=True,
            source="embedded",
        )
        self.assertEqual(data["source"], "embedded")
        self.assertEqual(data["frame_index"], -1)

    def test_source_defaults_to_frame(self):
        data = self._emit(
            poster_path="/tmp/p.jpg",
            frame_index=3,
            mode="manual",
            reasoning="",
            input_path="/tmp/v.mp4",
            use_json=True,
        )
        self.assertEqual(data["source"], "frame")


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