"""Tests for the metadata module (EXIF embedding and reading)."""

import io
import json
import os
import sys
import tempfile
import unittest

from PIL import Image


def _make_jpeg(path: str, width: int = 100, height: int = 100) -> None:
    """Create a minimal JPEG test image."""
    img = Image.new("RGB", (width, height), color=(128, 128, 128))
    img.save(path, "JPEG")


class TestBuildMetadata(unittest.TestCase):
    """Tests for metadata.build_metadata()."""

    def setUp(self):
        from video_thumbnail_creator.metadata import build_metadata
        self.build = build_metadata

    def test_required_fields_always_present(self):
        meta = self.build(
            source="frame", frame_index=5, crop_position="center",
            fmt="poster", mode="auto", input_file="/path/to/video.mp4",
        )
        self.assertIn("vtc_version", meta)
        self.assertIn("source", meta)
        self.assertIn("format", meta)
        self.assertIn("mode", meta)
        self.assertIn("input_file", meta)
        self.assertIn("created_at", meta)

    def test_input_file_is_basename_only(self):
        meta = self.build(
            source="frame", frame_index=0, crop_position="center",
            fmt="poster", mode="auto", input_file="/some/path/video.mp4",
        )
        self.assertEqual(meta["input_file"], "video.mp4")

    def test_frame_index_included_when_non_negative(self):
        meta = self.build(
            source="frame", frame_index=12, crop_position="center",
            fmt="poster", mode="auto", input_file="v.mp4",
        )
        self.assertEqual(meta["frame_index"], 12)

    def test_optional_fields_included_when_provided(self):
        meta = self.build(
            source="frame", frame_index=0, crop_position="center-left",
            fmt="poster", mode="auto", input_file="v.mp4",
            overlay_title="My Title", overlay_category="Nature",
            overlay_note="Episode 1", ai_reasoning="Sharp frame",
            poster_template="/path/to/template.toml",
        )
        self.assertEqual(meta["overlay_title"], "My Title")
        self.assertEqual(meta["overlay_category"], "Nature")
        self.assertEqual(meta["overlay_note"], "Episode 1")
        self.assertEqual(meta["ai_reasoning"], "Sharp frame")
        self.assertEqual(meta["poster_template"], "/path/to/template.toml")

    def test_optional_fields_omitted_when_empty(self):
        meta = self.build(
            source="frame", frame_index=0, crop_position="",
            fmt="poster", mode="auto", input_file="v.mp4",
        )
        self.assertNotIn("overlay_title", meta)
        self.assertNotIn("overlay_category", meta)
        self.assertNotIn("overlay_note", meta)
        self.assertNotIn("ai_reasoning", meta)
        self.assertNotIn("poster_template", meta)
        self.assertNotIn("crop_position", meta)

    def test_overlay_category_logo_uses_basename(self):
        meta = self.build(
            source="frame", frame_index=0, crop_position="center",
            fmt="poster", mode="auto", input_file="v.mp4",
            overlay_category_logo="/path/to/logo.png",
        )
        self.assertEqual(meta["overlay_category_logo"], "logo.png")


class TestBuildMetadataMinimal(unittest.TestCase):
    """Tests for edge cases in build_metadata."""

    def setUp(self):
        from video_thumbnail_creator.metadata import build_metadata
        self.build = build_metadata

    def test_frame_index_minus_one_excluded(self):
        meta = self.build(
            source="embedded", frame_index=-1, crop_position="center",
            fmt="poster", mode="auto", input_file="v.mp4",
        )
        self.assertNotIn("frame_index", meta)

    def test_source_embedded(self):
        meta = self.build(
            source="embedded", frame_index=-1, crop_position="",
            fmt="poster", mode="auto", input_file="v.mp4",
        )
        self.assertEqual(meta["source"], "embedded")


class TestEmbedAndReadMetadata(unittest.TestCase):
    """Round-trip tests: build → embed → read."""

    def test_embed_and_read_round_trip(self):
        from video_thumbnail_creator.metadata import build_metadata, embed_metadata, read_metadata

        with tempfile.TemporaryDirectory() as tmpdir:
            img_path = os.path.join(tmpdir, "poster.jpg")
            _make_jpeg(img_path)

            meta = build_metadata(
                source="frame", frame_index=12, crop_position="center-left",
                fmt="poster", mode="auto", input_file="video.mp4",
                overlay_title="Herbst-Spaziergang",
                overlay_category="Videoschnittstudio",
                overlay_note="1. November 2025",
                ai_reasoning="Sharp, well-lit frame.",
            )
            embed_metadata(img_path, meta)

            result = read_metadata(img_path)
            self.assertIsNotNone(result)
            self.assertEqual(result["source"], "frame")
            self.assertEqual(result["frame_index"], 12)
            self.assertEqual(result["crop_position"], "center-left")
            self.assertEqual(result["format"], "poster")
            self.assertEqual(result["overlay_title"], "Herbst-Spaziergang")
            self.assertEqual(result["overlay_category"], "Videoschnittstudio")
            self.assertEqual(result["overlay_note"], "1. November 2025")
            self.assertEqual(result["ai_reasoning"], "Sharp, well-lit frame.")
            self.assertIn("vtc_version", result)
            self.assertIn("created_at", result)

    def test_embed_sets_software_tag(self):
        from video_thumbnail_creator.metadata import build_metadata, embed_metadata

        with tempfile.TemporaryDirectory() as tmpdir:
            img_path = os.path.join(tmpdir, "poster.jpg")
            _make_jpeg(img_path)

            meta = build_metadata(
                source="frame", frame_index=0, crop_position="center",
                fmt="poster", mode="auto", input_file="v.mp4",
            )
            embed_metadata(img_path, meta)

            img = Image.open(img_path)
            exif = img.getexif()
            img.close()
            software = exif.get(0x0131, "")
            self.assertIn("video-thumbnail-creator", software)

    def test_embed_sets_image_description_when_title_present(self):
        from video_thumbnail_creator.metadata import build_metadata, embed_metadata

        with tempfile.TemporaryDirectory() as tmpdir:
            img_path = os.path.join(tmpdir, "poster.jpg")
            _make_jpeg(img_path)

            meta = build_metadata(
                source="frame", frame_index=0, crop_position="center",
                fmt="poster", mode="auto", input_file="v.mp4",
                overlay_title="My Title",
            )
            embed_metadata(img_path, meta)

            img = Image.open(img_path)
            exif = img.getexif()
            img.close()
            self.assertEqual(exif.get(0x010E), "My Title")

    def test_embed_sets_artist_when_category_present(self):
        from video_thumbnail_creator.metadata import build_metadata, embed_metadata

        with tempfile.TemporaryDirectory() as tmpdir:
            img_path = os.path.join(tmpdir, "poster.jpg")
            _make_jpeg(img_path)

            meta = build_metadata(
                source="frame", frame_index=0, crop_position="center",
                fmt="poster", mode="auto", input_file="v.mp4",
                overlay_category="Nature",
            )
            embed_metadata(img_path, meta)

            img = Image.open(img_path)
            exif = img.getexif()
            img.close()
            self.assertEqual(exif.get(0x013B), "Nature")


class TestReadMetadataNoVtc(unittest.TestCase):
    """Test read_metadata returns None for non-VTC images."""

    def test_plain_jpeg_returns_none(self):
        from video_thumbnail_creator.metadata import read_metadata

        with tempfile.TemporaryDirectory() as tmpdir:
            img_path = os.path.join(tmpdir, "plain.jpg")
            _make_jpeg(img_path)

            result = read_metadata(img_path)
            self.assertIsNone(result)

    def test_nonexistent_file_returns_none(self):
        from video_thumbnail_creator.metadata import read_metadata

        result = read_metadata("/nonexistent/path/image.jpg")
        self.assertIsNone(result)


class TestInfoCommandJSON(unittest.TestCase):
    """Tests for the CLI info subcommand."""

    def test_info_json_output(self):
        from video_thumbnail_creator.metadata import build_metadata, embed_metadata
        from video_thumbnail_creator.cli import _build_parser, _handle_info_command

        with tempfile.TemporaryDirectory() as tmpdir:
            img_path = os.path.join(tmpdir, "poster.jpg")
            _make_jpeg(img_path)

            meta = build_metadata(
                source="frame", frame_index=5, crop_position="center",
                fmt="poster", mode="auto", input_file="video.mp4",
                overlay_title="Test Title",
            )
            embed_metadata(img_path, meta)

            parser = _build_parser()
            args = parser.parse_args(["info", "--json", img_path])

            buf = io.StringIO()
            old_stdout = sys.stdout
            sys.stdout = buf
            try:
                _handle_info_command(args)
            finally:
                sys.stdout = old_stdout

            data = json.loads(buf.getvalue())
            self.assertIn("vtc_version", data)
            self.assertEqual(data["source"], "frame")
            self.assertEqual(data["frame_index"], 5)
            self.assertEqual(data["overlay_title"], "Test Title")

    def test_info_human_readable_output(self):
        from video_thumbnail_creator.metadata import build_metadata, embed_metadata
        from video_thumbnail_creator.cli import _build_parser, _handle_info_command

        with tempfile.TemporaryDirectory() as tmpdir:
            img_path = os.path.join(tmpdir, "poster.jpg")
            _make_jpeg(img_path)

            meta = build_metadata(
                source="frame", frame_index=3, crop_position="left",
                fmt="poster", mode="manual", input_file="myvideo.mp4",
            )
            embed_metadata(img_path, meta)

            parser = _build_parser()
            args = parser.parse_args(["info", img_path])

            buf = io.StringIO()
            old_stdout = sys.stdout
            sys.stdout = buf
            try:
                _handle_info_command(args)
            finally:
                sys.stdout = old_stdout

            output = buf.getvalue()
            self.assertIn("Poster Metadata:", output)
            self.assertIn("frame", output)


class TestInfoCommandMissingFile(unittest.TestCase):
    """Tests for error handling in the info subcommand."""

    def test_missing_file_exits_with_error(self):
        from video_thumbnail_creator.cli import _build_parser, _handle_info_command

        parser = _build_parser()
        args = parser.parse_args(["info", "/nonexistent/poster.jpg"])

        with self.assertRaises(SystemExit) as ctx:
            _handle_info_command(args)
        self.assertNotEqual(ctx.exception.code, 0)

    def test_no_vtc_metadata_exits_with_error(self):
        from video_thumbnail_creator.cli import _build_parser, _handle_info_command

        with tempfile.TemporaryDirectory() as tmpdir:
            img_path = os.path.join(tmpdir, "plain.jpg")
            _make_jpeg(img_path)

            parser = _build_parser()
            args = parser.parse_args(["info", img_path])

            with self.assertRaises(SystemExit) as ctx:
                _handle_info_command(args)
            self.assertNotEqual(ctx.exception.code, 0)


class TestInfoSubcommandParser(unittest.TestCase):
    """Tests for the info subcommand parser."""

    def test_info_command_parsed(self):
        from video_thumbnail_creator.cli import _build_parser
        parser = _build_parser()
        args = parser.parse_args(["info", "/path/to/poster.jpg"])
        self.assertEqual(args.command, "info")
        self.assertEqual(args.image_path, "/path/to/poster.jpg")
        self.assertFalse(args.json)

    def test_info_command_json_flag(self):
        from video_thumbnail_creator.cli import _build_parser
        parser = _build_parser()
        args = parser.parse_args(["info", "--json", "/path/to/poster.jpg"])
        self.assertTrue(args.json)


class TestToAsciiSafe(unittest.TestCase):
    """Tests for the _to_ascii_safe() helper function."""

    def setUp(self):
        from video_thumbnail_creator.metadata import _to_ascii_safe
        self.fn = _to_ascii_safe

    def test_german_umlauts(self):
        self.assertEqual(self.fn("Mädchen"), "Maedchen")
        self.assertEqual(self.fn("Glück"), "Glueck")
        self.assertEqual(self.fn("Öffnung"), "Oeffnung")
        self.assertEqual(self.fn("Straße"), "Strasse")
        self.assertEqual(self.fn("Ärger"), "Aerger")
        self.assertEqual(self.fn("Über"), "Ueber")

    def test_french_accents(self):
        self.assertEqual(self.fn("café"), "cafe")
        self.assertEqual(self.fn("crème"), "creme")
        self.assertEqual(self.fn("fête"), "fete")
        self.assertEqual(self.fn("à la"), "a la")
        self.assertEqual(self.fn("château"), "chateau")

    def test_ascii_unchanged(self):
        self.assertEqual(self.fn("Hello World"), "Hello World")
        self.assertEqual(self.fn("Nature"), "Nature")
        self.assertEqual(self.fn("My Title"), "My Title")

    def test_mixed_text(self):
        self.assertEqual(self.fn("Familie Kurmann-Glück"), "Familie Kurmann-Glueck")
        self.assertEqual(self.fn("Mädchen Memory"), "Maedchen Memory")


class TestUnicodeRoundTrip(unittest.TestCase):
    """Round-trip tests with Unicode (Umlauts) in metadata fields."""

    def test_unicode_preserved_in_user_comment(self):
        """Umlauts are preserved in the UserComment JSON (UTF-16 encoded)."""
        from video_thumbnail_creator.metadata import build_metadata, embed_metadata, read_metadata

        with tempfile.TemporaryDirectory() as tmpdir:
            img_path = os.path.join(tmpdir, "poster.jpg")
            _make_jpeg(img_path)

            meta = build_metadata(
                source="frame", frame_index=1, crop_position="center",
                fmt="poster", mode="auto", input_file="video.mp4",
                overlay_title="Mädchen Memory",
                overlay_category="Familie Kurmann-Glück",
                overlay_note="Schöner Abend",
            )
            embed_metadata(img_path, meta)

            result = read_metadata(img_path)
            self.assertIsNotNone(result)
            # Original Unicode values must be preserved in the JSON UserComment
            self.assertEqual(result["overlay_title"], "Mädchen Memory")
            self.assertEqual(result["overlay_category"], "Familie Kurmann-Glück")
            self.assertEqual(result["overlay_note"], "Schöner Abend")

    def test_ifd0_tags_are_ascii_safe(self):
        """IFD0 ImageDescription and Artist use ASCII-safe transliteration."""
        from video_thumbnail_creator.metadata import build_metadata, embed_metadata

        with tempfile.TemporaryDirectory() as tmpdir:
            img_path = os.path.join(tmpdir, "poster.jpg")
            _make_jpeg(img_path)

            meta = build_metadata(
                source="frame", frame_index=0, crop_position="center",
                fmt="poster", mode="auto", input_file="v.mp4",
                overlay_title="Mädchen Memory",
                overlay_category="Familie Kurmann-Glück",
            )
            embed_metadata(img_path, meta)

            img = Image.open(img_path)
            exif = img.getexif()
            img.close()
            self.assertEqual(exif.get(0x010E), "Maedchen Memory")
            self.assertEqual(exif.get(0x013B), "Familie Kurmann-Glueck")

    def test_user_comment_bytes_have_unicode_prefix(self):
        """UserComment bytes must start with the UNICODE\\x00 charset prefix."""
        from video_thumbnail_creator.metadata import build_metadata, embed_metadata

        with tempfile.TemporaryDirectory() as tmpdir:
            img_path = os.path.join(tmpdir, "poster.jpg")
            _make_jpeg(img_path)

            meta = build_metadata(
                source="frame", frame_index=0, crop_position="center",
                fmt="poster", mode="auto", input_file="v.mp4",
                overlay_title="Mädchen Memory",
            )
            embed_metadata(img_path, meta)

            img = Image.open(img_path)
            exif = img.getexif()
            exif_ifd = exif.get_ifd(0x8769)
            user_comment = exif_ifd.get(0x9286, b"")
            img.close()

            self.assertIsInstance(user_comment, bytes)
            self.assertTrue(user_comment[:8] == b"UNICODE\x00",
                            f"Expected UNICODE\\x00 prefix, got: {user_comment[:8]!r}")

    def test_read_metadata_old_format_fallback(self):
        """read_metadata() handles plain string UserComment (old format) gracefully."""
        from video_thumbnail_creator.metadata import read_metadata

        with tempfile.TemporaryDirectory() as tmpdir:
            img_path = os.path.join(tmpdir, "poster.jpg")
            _make_jpeg(img_path)

            # Write old-style UserComment (no charset prefix, plain string via Pillow)
            img = Image.open(img_path)
            exif = img.getexif()
            exif_ifd = exif.get_ifd(0x8769)
            old_meta = {
                "vtc_version": "1.2.0",
                "source": "frame",
                "format": "poster",
                "mode": "auto",
                "input_file": "video.mp4",
                "created_at": "2025-01-01T00:00:00",
                "overlay_title": "Old Title",
            }
            # Simulate old format: plain UTF-8 bytes without charset prefix
            exif_ifd[0x9286] = json.dumps(old_meta).encode("utf-8")
            img.save(img_path, exif=exif.tobytes(), quality=95)
            img.close()

            result = read_metadata(img_path)
            self.assertIsNotNone(result)
            self.assertEqual(result["vtc_version"], "1.2.0")
            self.assertEqual(result["overlay_title"], "Old Title")


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