"""Tests for crop_selector, poster_composer, and CLI new options."""

import os
import tempfile
import unittest

from PIL import Image


# ---------------------------------------------------------------------------
# crop_selector tests
# ---------------------------------------------------------------------------

class TestApplyCropPosition(unittest.TestCase):
    """Tests for crop_selector.apply_crop_position()."""

    def setUp(self):
        from video_thumbnail_creator.crop_selector import apply_crop_position
        self.apply = apply_crop_position

    def test_left_gives_x_offset_zero(self):
        box = self.apply(1920, 1080, "left")
        self.assertEqual(box, (0, 0, 1080, 1080))

    def test_right_gives_max_x_offset(self):
        box = self.apply(1920, 1080, "right")
        # max_offset = 1920 - 1080 = 840
        self.assertEqual(box, (840, 0, 1920, 1080))

    def test_center_gives_middle_x_offset(self):
        box = self.apply(1920, 1080, "center")
        # max_offset = 840; center = 840 // 2 = 420
        self.assertEqual(box, (420, 0, 1500, 1080))

    def test_center_left_gives_quarter_x_offset(self):
        box = self.apply(1920, 1080, "center-left")
        # max_offset = 840; 1/4 = 210
        self.assertEqual(box, (210, 0, 1290, 1080))

    def test_center_right_gives_three_quarter_x_offset(self):
        box = self.apply(1920, 1080, "center-right")
        # max_offset = 840; 3/4 = 630
        self.assertEqual(box, (630, 0, 1710, 1080))

    def test_crop_box_is_square(self):
        for pos in ["left", "center-left", "center", "center-right", "right"]:
            box = self.apply(1920, 1080, pos)
            width = box[2] - box[0]
            height = box[3] - box[1]
            self.assertEqual(width, height, f"Expected square box for position '{pos}'")

    def test_invalid_position_raises_value_error(self):
        with self.assertRaises(ValueError):
            self.apply(1920, 1080, "top")

    def test_works_with_non_16x9_source(self):
        # 1280×720 → crop size = 720
        box = self.apply(1280, 720, "center")
        # max_offset = 1280 - 720 = 560; center = 280
        self.assertEqual(box, (280, 0, 1000, 720))

    def test_square_image_all_positions_give_same_box(self):
        """If the image is already 1:1, all positions should give the same crop."""
        for pos in ["left", "center-left", "center", "center-right", "right"]:
            box = self.apply(1080, 1080, pos)
            self.assertEqual(box, (0, 0, 1080, 1080))


# ---------------------------------------------------------------------------
# poster_composer tests
# ---------------------------------------------------------------------------

class TestComposePoster(unittest.TestCase):
    """Tests for poster_composer.compose_poster()."""

    def _make_frame(self, path: str, width: int = 1920, height: int = 1080) -> None:
        img = Image.new("RGB", (width, height), color=(100, 150, 200))
        img.save(path, "JPEG")

    def test_compose_poster_produces_file(self):
        from video_thumbnail_creator.poster_composer import compose_poster

        with tempfile.TemporaryDirectory() as tmpdir:
            frame = os.path.join(tmpdir, "frame.jpg")
            self._make_frame(frame)
            out = os.path.join(tmpdir, "poster.jpg")
            result = compose_poster(frame, "center", None, out)
            self.assertTrue(os.path.isfile(result))

    def test_compose_poster_correct_dimensions(self):
        from video_thumbnail_creator.poster_composer import (
            compose_poster,
            POSTER_WIDTH,
            POSTER_HEIGHT,
        )

        with tempfile.TemporaryDirectory() as tmpdir:
            frame = os.path.join(tmpdir, "frame.jpg")
            self._make_frame(frame)
            out = os.path.join(tmpdir, "poster.jpg")
            compose_poster(frame, "center", None, out)
            with Image.open(out) as img:
                self.assertEqual(img.size, (POSTER_WIDTH, POSTER_HEIGHT))

    def test_compose_poster_with_text_correct_dimensions(self):
        from video_thumbnail_creator.poster_composer import (
            compose_poster,
            POSTER_WIDTH,
            POSTER_HEIGHT,
        )

        with tempfile.TemporaryDirectory() as tmpdir:
            frame = os.path.join(tmpdir, "frame.jpg")
            self._make_frame(frame)
            out = os.path.join(tmpdir, "poster.jpg")
            compose_poster(frame, "center", "My Video Title", out)
            with Image.open(out) as img:
                self.assertEqual(img.size, (POSTER_WIDTH, POSTER_HEIGHT))

    def test_compose_poster_all_crop_positions(self):
        from video_thumbnail_creator.crop_selector import CROP_POSITIONS
        from video_thumbnail_creator.poster_composer import compose_poster, POSTER_WIDTH, POSTER_HEIGHT

        with tempfile.TemporaryDirectory() as tmpdir:
            frame = os.path.join(tmpdir, "frame.jpg")
            self._make_frame(frame)
            for pos in CROP_POSITIONS:
                out = os.path.join(tmpdir, f"poster_{pos}.jpg")
                compose_poster(frame, pos, None, out)
                with Image.open(out) as img:
                    self.assertEqual(img.size, (POSTER_WIDTH, POSTER_HEIGHT))


class TestComposeLandscapeWithText(unittest.TestCase):
    """Tests for poster_composer.compose_landscape_with_text()."""

    def _make_frame(self, path: str, width: int = 1920, height: int = 1080) -> None:
        img = Image.new("RGB", (width, height), color=(100, 150, 200))
        img.save(path, "JPEG")

    def test_landscape_with_text_produces_file(self):
        from video_thumbnail_creator.poster_composer import compose_landscape_with_text

        with tempfile.TemporaryDirectory() as tmpdir:
            frame = os.path.join(tmpdir, "frame.jpg")
            self._make_frame(frame)
            out = os.path.join(tmpdir, "landscape.jpg")
            result = compose_landscape_with_text(frame, "Some Title", out)
            self.assertTrue(os.path.isfile(result))

    def test_landscape_with_text_correct_dimensions(self):
        from video_thumbnail_creator.poster_composer import (
            compose_landscape_with_text,
            LANDSCAPE_WIDTH,
            LANDSCAPE_HEIGHT,
        )

        with tempfile.TemporaryDirectory() as tmpdir:
            frame = os.path.join(tmpdir, "frame.jpg")
            self._make_frame(frame)
            out = os.path.join(tmpdir, "landscape.jpg")
            compose_landscape_with_text(frame, "Some Title", out)
            with Image.open(out) as img:
                self.assertEqual(img.size, (LANDSCAPE_WIDTH, LANDSCAPE_HEIGHT))

    def test_landscape_with_long_text(self):
        from video_thumbnail_creator.poster_composer import (
            compose_landscape_with_text,
            LANDSCAPE_WIDTH,
            LANDSCAPE_HEIGHT,
        )

        with tempfile.TemporaryDirectory() as tmpdir:
            frame = os.path.join(tmpdir, "frame.jpg")
            self._make_frame(frame)
            out = os.path.join(tmpdir, "landscape.jpg")
            long_text = "This Is A Very Long Title That Should Still Fit Within The Image Bounds"
            compose_landscape_with_text(frame, long_text, out)
            with Image.open(out) as img:
                self.assertEqual(img.size, (LANDSCAPE_WIDTH, LANDSCAPE_HEIGHT))


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

class TestCLINewArguments(unittest.TestCase):
    """Tests for the new CLI arguments."""

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

    def test_format_option_poster(self):
        parser = self._build_parser()
        args = parser.parse_args(["extract", "video.mp4", "--format", "poster"])
        self.assertEqual(args.format, "poster")

    def test_format_option_landscape(self):
        parser = self._build_parser()
        args = parser.parse_args(["extract", "video.mp4", "--format", "landscape"])
        self.assertEqual(args.format, "landscape")

    def test_format_default_is_none(self):
        """Default is None; resolved in main() from config."""
        parser = self._build_parser()
        args = parser.parse_args(["extract", "video.mp4"])
        self.assertIsNone(args.format)

    def test_format_invalid_choice_raises(self):
        parser = self._build_parser()
        with self.assertRaises(SystemExit):
            parser.parse_args(["extract", "video.mp4", "--format", "square"])

    def test_overlay_text_option(self):
        parser = self._build_parser()
        args = parser.parse_args(["extract", "video.mp4", "--overlay-title", "Hello World"])
        self.assertEqual(args.overlay_title, "Hello World")

    def test_overlay_text_from_filename_flag(self):
        parser = self._build_parser()
        args = parser.parse_args(["extract", "video.mp4", "--overlay-title-from-filename"])
        self.assertTrue(args.overlay_title_from_filename)

    def test_overlay_text_from_filename_default_false(self):
        parser = self._build_parser()
        args = parser.parse_args(["extract", "video.mp4"])
        self.assertFalse(args.overlay_title_from_filename)


class TestResolveOverlayTitle(unittest.TestCase):
    """Tests for the _resolve_overlay_title helper."""

    def _make_args(self, overlay_title=None, overlay_title_from_filename=False, video_path="video.mp4"):
        import argparse
        args = argparse.Namespace(
            overlay_title=overlay_title,
            overlay_title_from_filename=overlay_title_from_filename,
            input_path=video_path,
        )
        return args

    def test_explicit_text_takes_precedence(self):
        from video_thumbnail_creator.cli import _resolve_overlay_title
        args = self._make_args(
            overlay_title="My Title",
            overlay_title_from_filename=True,
            video_path="2025-01-01_MyVideo.mkv",
        )
        self.assertEqual(_resolve_overlay_title(args), "My Title")

    def test_from_filename_uses_stem(self):
        from video_thumbnail_creator.cli import _resolve_overlay_title
        args = self._make_args(
            overlay_title_from_filename=True,
            video_path="/path/to/2025-05-15_Starship_IFT7.mkv",
        )
        self.assertEqual(_resolve_overlay_title(args), "2025-05-15_Starship_IFT7")

    def test_neither_returns_empty_string(self):
        from video_thumbnail_creator.cli import _resolve_overlay_title
        args = self._make_args()
        self.assertEqual(_resolve_overlay_title(args), "")


class TestEmitResultJSON(unittest.TestCase):
    """Tests for _emit_result JSON output including new fields."""

    def test_json_includes_format_field(self):
        import io
        from contextlib import redirect_stdout
        from video_thumbnail_creator.cli import _emit_result
        import json

        buf = io.StringIO()
        with redirect_stdout(buf):
            _emit_result(
                "/tmp/poster.jpg", 5, "auto", "Good frame", "/tmp/video.mp4",
                use_json=True, fmt="poster", crop_position="center", overlay_title="Title",
            )
        data = json.loads(buf.getvalue())
        self.assertEqual(data["format"], "poster")
        self.assertEqual(data["crop_position"], "center")
        self.assertEqual(data["overlay_title"], "Title")

    def test_json_omits_empty_crop_position(self):
        import io
        from contextlib import redirect_stdout
        from video_thumbnail_creator.cli import _emit_result
        import json

        buf = io.StringIO()
        with redirect_stdout(buf):
            _emit_result(
                "/tmp/poster.jpg", 5, "manual", "", "/tmp/video.mp4",
                use_json=True, fmt="landscape",
            )
        data = json.loads(buf.getvalue())
        self.assertNotIn("crop_position", data)
        self.assertNotIn("overlay_text", data)


class TestComposePosterNewFeatures(unittest.TestCase):
    """Tests for the new poster features: category, logo, note, vignette."""

    def _make_frame(self, path: str, width: int = 1920, height: int = 1080) -> None:
        img = Image.new("RGB", (width, height), color=(100, 150, 200))
        img.save(path, "JPEG")

    def _make_logo(self, path: str, width: int, height: int) -> None:
        img = Image.new("RGBA", (width, height), color=(255, 100, 50, 200))
        img.save(path, "PNG")

    def test_compose_poster_with_category_correct_dimensions(self):
        from video_thumbnail_creator.poster_composer import compose_poster, POSTER_WIDTH, POSTER_HEIGHT

        with tempfile.TemporaryDirectory() as tmpdir:
            frame = os.path.join(tmpdir, "frame.jpg")
            self._make_frame(frame)
            out = os.path.join(tmpdir, "poster.jpg")
            compose_poster(frame, "center", "My Title", out, overlay_category="Documentaries")
            with Image.open(out) as img:
                self.assertEqual(img.size, (POSTER_WIDTH, POSTER_HEIGHT))

    def test_compose_poster_with_note_correct_dimensions(self):
        from video_thumbnail_creator.poster_composer import compose_poster, POSTER_WIDTH, POSTER_HEIGHT

        with tempfile.TemporaryDirectory() as tmpdir:
            frame = os.path.join(tmpdir, "frame.jpg")
            self._make_frame(frame)
            out = os.path.join(tmpdir, "poster.jpg")
            compose_poster(frame, "center", "My Title", out, overlay_note="Episode 1")
            with Image.open(out) as img:
                self.assertEqual(img.size, (POSTER_WIDTH, POSTER_HEIGHT))

    def test_compose_poster_with_wide_logo(self):
        from video_thumbnail_creator.poster_composer import compose_poster, POSTER_WIDTH, POSTER_HEIGHT

        with tempfile.TemporaryDirectory() as tmpdir:
            frame = os.path.join(tmpdir, "frame.jpg")
            self._make_frame(frame)
            logo = os.path.join(tmpdir, "logo_wide.png")
            self._make_logo(logo, 200, 60)  # wide: 200 > 60*1.3
            out = os.path.join(tmpdir, "poster.jpg")
            compose_poster(frame, "center", "My Title", out, overlay_category_logo_path=logo)
            with Image.open(out) as img:
                self.assertEqual(img.size, (POSTER_WIDTH, POSTER_HEIGHT))

    def test_compose_poster_with_square_logo(self):
        from video_thumbnail_creator.poster_composer import compose_poster, POSTER_WIDTH, POSTER_HEIGHT

        with tempfile.TemporaryDirectory() as tmpdir:
            frame = os.path.join(tmpdir, "frame.jpg")
            self._make_frame(frame)
            logo = os.path.join(tmpdir, "logo_square.png")
            self._make_logo(logo, 80, 80)  # square: 80 == 80*1.0
            out = os.path.join(tmpdir, "poster.jpg")
            compose_poster(frame, "center", "My Title", out, overlay_category_logo_path=logo)
            with Image.open(out) as img:
                self.assertEqual(img.size, (POSTER_WIDTH, POSTER_HEIGHT))

    def test_compose_poster_all_elements_combined(self):
        from video_thumbnail_creator.poster_composer import compose_poster, POSTER_WIDTH, POSTER_HEIGHT

        with tempfile.TemporaryDirectory() as tmpdir:
            frame = os.path.join(tmpdir, "frame.jpg")
            self._make_frame(frame)
            out = os.path.join(tmpdir, "poster.jpg")
            result = compose_poster(
                frame, "center", "Starship IFT-7", out,
                overlay_category="Space Exploration", overlay_note="2025-05-15",
            )
            self.assertTrue(os.path.isfile(result))
            with Image.open(result) as img:
                self.assertEqual(img.size, (POSTER_WIDTH, POSTER_HEIGHT))

    def test_compose_poster_note_only_no_title(self):
        """Note should render even without overlay_title."""
        from video_thumbnail_creator.poster_composer import compose_poster, POSTER_WIDTH, POSTER_HEIGHT

        with tempfile.TemporaryDirectory() as tmpdir:
            frame = os.path.join(tmpdir, "frame.jpg")
            self._make_frame(frame)
            out = os.path.join(tmpdir, "poster.jpg")
            compose_poster(frame, "center", None, out, overlay_note="Just a note")
            with Image.open(out) as img:
                self.assertEqual(img.size, (POSTER_WIDTH, POSTER_HEIGHT))


class TestResolveCategoryHelper(unittest.TestCase):
    """Tests for the _resolve_category helper."""

    def _make_args(self, overlay_category=None, overlay_category_logo=None):
        import argparse
        return argparse.Namespace(overlay_category=overlay_category, overlay_category_logo=overlay_category_logo)

    def test_category_only(self):
        from video_thumbnail_creator.cli import _resolve_category
        args = self._make_args(overlay_category="Nature")
        cat, logo = _resolve_category(args)
        self.assertEqual(cat, "Nature")
        self.assertIsNone(logo)

    def test_logo_only(self):
        from video_thumbnail_creator.cli import _resolve_category
        args = self._make_args(overlay_category_logo="/path/to/logo.png")
        cat, logo = _resolve_category(args)
        self.assertIsNone(cat)
        self.assertEqual(logo, "/path/to/logo.png")

    def test_both_logo_takes_precedence_with_warning(self):
        import io
        from video_thumbnail_creator.cli import _resolve_category
        args = self._make_args(overlay_category="Nature", overlay_category_logo="/path/to/logo.png")
        buf = io.StringIO()
        import sys
        old_stderr = sys.stderr
        sys.stderr = buf
        cat, logo = _resolve_category(args)
        sys.stderr = old_stderr
        self.assertIsNone(cat)
        self.assertEqual(logo, "/path/to/logo.png")
        self.assertIn("logo takes precedence", buf.getvalue())

    def test_neither_returns_none_none(self):
        from video_thumbnail_creator.cli import _resolve_category
        args = self._make_args()
        cat, logo = _resolve_category(args)
        self.assertIsNone(cat)
        self.assertIsNone(logo)


class TestCLINewPosterArguments(unittest.TestCase):
    """Tests for the new --overlay-category, --overlay-category-logo, --overlay-note CLI arguments."""

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

    def test_category_option(self):
        parser = self._build_parser()
        args = parser.parse_args(["extract", "video.mp4", "--overlay-category", "Nature"])
        self.assertEqual(args.overlay_category, "Nature")

    def test_category_logo_option(self):
        parser = self._build_parser()
        args = parser.parse_args(["extract", "video.mp4", "--overlay-category-logo", "/tmp/logo.png"])
        self.assertEqual(args.overlay_category_logo, "/tmp/logo.png")

    def test_note_option(self):
        parser = self._build_parser()
        args = parser.parse_args(["extract", "video.mp4", "--overlay-note", "Episode 1"])
        self.assertEqual(args.overlay_note, "Episode 1")

    def test_category_default_is_none(self):
        parser = self._build_parser()
        args = parser.parse_args(["extract", "video.mp4"])
        self.assertIsNone(args.overlay_category)
        self.assertIsNone(args.overlay_category_logo)
        self.assertIsNone(args.overlay_note)


class TestEmitResultJSONNewFields(unittest.TestCase):
    """Tests for _emit_result JSON output including new overlay_category and overlay_note fields."""

    def test_json_includes_category_and_note(self):
        import io, json
        from contextlib import redirect_stdout
        from video_thumbnail_creator.cli import _emit_result

        buf = io.StringIO()
        with redirect_stdout(buf):
            _emit_result(
                "/tmp/poster.jpg", 5, "auto", "Good frame", "/tmp/video.mp4",
                use_json=True, fmt="poster", crop_position="center",
                overlay_title="Title", overlay_category="Documentaries", overlay_note="Ep. 3",
            )
        data = json.loads(buf.getvalue())
        self.assertEqual(data["overlay_category"], "Documentaries")
        self.assertEqual(data["overlay_note"], "Ep. 3")

    def test_json_omits_empty_category_and_note(self):
        import io, json
        from contextlib import redirect_stdout
        from video_thumbnail_creator.cli import _emit_result

        buf = io.StringIO()
        with redirect_stdout(buf):
            _emit_result(
                "/tmp/poster.jpg", 5, "auto", "Good frame", "/tmp/video.mp4",
                use_json=True, fmt="poster",
            )
        data = json.loads(buf.getvalue())
        self.assertNotIn("overlay_category", data)
        self.assertNotIn("overlay_note", data)


# ---------------------------------------------------------------------------
# poster_template tests
# ---------------------------------------------------------------------------

class TestPosterTemplate(unittest.TestCase):
    """Tests for poster_template style registry and helpers."""

    def test_builtin_template_has_all_sections(self):
        from video_thumbnail_creator.poster_template import _BUILTIN_TEMPLATE
        expected_sections = {
            "poster", "separator", "frame", "title", "category",
            "note", "logo", "shadow", "landscape", "text_area", "font", "badges",
        }
        self.assertEqual(set(_BUILTIN_TEMPLATE.keys()), expected_sections)

    def test_get_style_default_returns_builtin(self):
        from video_thumbnail_creator.poster_template import get_style
        result = get_style("internet")
        self.assertTrue(result["poster"]["category_at_top"])
        self.assertFalse(result["text_area"]["blur_enabled"])

    def test_get_style_returns_deep_copy(self):
        from video_thumbnail_creator.poster_template import get_style
        a = get_style("internet")
        b = get_style("internet")
        a["poster"]["width"] = 9999
        self.assertNotEqual(b["poster"]["width"], 9999)

    def test_get_style_internet_exists(self):
        from video_thumbnail_creator.poster_template import get_style
        t = get_style("internet")
        self.assertTrue(t["poster"]["category_at_top"])
        self.assertFalse(t["text_area"]["blur_enabled"])
        self.assertGreater(t["title"]["max_font_size"], 100)

    def test_get_style_unknown_raises(self):
        from video_thumbnail_creator.poster_template import get_style
        with self.assertRaises(ValueError):
            get_style("nonexistent_style")

    def test_list_styles_contains_internet(self):
        from video_thumbnail_creator.poster_template import list_styles
        names = [s["name"] for s in list_styles()]
        self.assertIn("internet", names)
        self.assertNotIn("default", names)

    def test_list_styles_has_descriptions(self):
        from video_thumbnail_creator.poster_template import list_styles
        for s in list_styles():
            self.assertIn("name", s)
            self.assertIn("description", s)
            self.assertTrue(len(s["description"]) > 0)

    def test_deep_merge(self):
        from video_thumbnail_creator.poster_template import _deep_merge
        base = {"a": {"x": 1, "y": 2}, "b": 3}
        override = {"a": {"y": 99, "z": 100}, "c": 4}
        result = _deep_merge(base, override)
        self.assertEqual(result["a"]["x"], 1)
        self.assertEqual(result["a"]["y"], 99)
        self.assertEqual(result["a"]["z"], 100)
        self.assertEqual(result["b"], 3)
        self.assertEqual(result["c"], 4)

    def test_builtin_template_frame_has_corner_radius(self):
        from video_thumbnail_creator.poster_template import _BUILTIN_TEMPLATE
        frame = _BUILTIN_TEMPLATE["frame"]
        self.assertIn("corner_radius", frame)
        self.assertEqual(frame["corner_radius"], 0)

    def test_builtin_template_separator_height_is_10(self):
        from video_thumbnail_creator.poster_template import _BUILTIN_TEMPLATE
        self.assertEqual(_BUILTIN_TEMPLATE["separator"]["height"], 10)

    def test_style_cli_option(self):
        from video_thumbnail_creator.cli_parser import _build_parser
        parser = _build_parser()
        args = parser.parse_args(["extract", "video.mp4", "--style", "internet"])
        self.assertEqual(args.style, "internet")

    def test_style_cli_default_is_internet(self):
        from video_thumbnail_creator.cli_parser import _build_parser
        parser = _build_parser()
        args = parser.parse_args(["extract", "video.mp4"])
        self.assertEqual(args.style, "internet")

    def test_builtin_template_text_area_keys(self):
        from video_thumbnail_creator.poster_template import _BUILTIN_TEMPLATE
        text_area = _BUILTIN_TEMPLATE["text_area"]
        self.assertIn("darken_opacity", text_area)
        self.assertIn("blur_radius", text_area)
        self.assertIn("background_color", text_area)
        self.assertAlmostEqual(text_area["darken_opacity"], 0.4)
        self.assertEqual(text_area["blur_radius"], 30)
        self.assertEqual(text_area["background_color"], [26, 26, 26])

    def test_builtin_template_font_keys(self):
        from video_thumbnail_creator.poster_template import _BUILTIN_TEMPLATE
        font = _BUILTIN_TEMPLATE["font"]
        self.assertIn("family", font)
        self.assertIn("bold_suffix", font)
        self.assertIn("regular_suffix", font)
        self.assertEqual(font["family"], "Helvetica Neue")
        self.assertEqual(font["bold_suffix"], " Bold")
        self.assertEqual(font["regular_suffix"], "")


class TestRoundedFrameAndSolidBackground(unittest.TestCase):
    """Tests for rounded frame corners and solid dark background."""

    def _make_frame(self, path: str, color=(100, 150, 200)) -> None:
        img = Image.new("RGB", (1920, 1080), color=color)
        img.save(path, "JPEG")

    def test_missing_corner_radius_defaults_to_zero(self):
        """If corner_radius is missing from template, defaults to 0 (square corners)."""
        from video_thumbnail_creator.poster_composer import compose_poster, POSTER_WIDTH, POSTER_HEIGHT
        from video_thumbnail_creator.poster_template import _BUILTIN_TEMPLATE
        import copy

        template = copy.deepcopy(_BUILTIN_TEMPLATE)
        del template["frame"]["corner_radius"]

        with tempfile.TemporaryDirectory() as tmpdir:
            frame = os.path.join(tmpdir, "frame.jpg")
            self._make_frame(frame)
            out = os.path.join(tmpdir, "poster.jpg")
            compose_poster(frame, "center", "Title", out, template=template)
            with Image.open(out) as img:
                self.assertEqual(img.size, (POSTER_WIDTH, POSTER_HEIGHT))

    def test_separator_not_drawn_when_height_zero(self):
        """When separator height=0, the pixel at poster_square y should NOT be separator color."""
        from video_thumbnail_creator.poster_composer import compose_poster
        from video_thumbnail_creator.poster_template import _BUILTIN_TEMPLATE
        import copy

        template = copy.deepcopy(_BUILTIN_TEMPLATE)
        template["separator"]["height"] = 0
        sep_color = (42, 42, 42)
        template["separator"]["color"] = list(sep_color)
        template["text_area"]["background_color"] = [26, 26, 26]

        with tempfile.TemporaryDirectory() as tmpdir:
            frame = os.path.join(tmpdir, "frame.jpg")
            self._make_frame(frame)
            out = os.path.join(tmpdir, "poster.jpg")
            compose_poster(frame, "center", None, out, template=template)

            image_height = _BUILTIN_TEMPLATE["poster"]["image_height"]
            sample_x = _BUILTIN_TEMPLATE["poster"]["width"] // 2

            with Image.open(out) as img:
                pixel = img.getpixel((sample_x, image_height))

            # Pixel should NOT be the separator color (it should be dark background)
            self.assertNotEqual(pixel[:3], sep_color)

    def test_custom_background_color_applied(self):
        """A custom background_color in text_area should produce a valid poster."""
        from video_thumbnail_creator.poster_composer import compose_poster, POSTER_WIDTH, POSTER_HEIGHT
        from video_thumbnail_creator.poster_template import _BUILTIN_TEMPLATE
        import copy

        custom_color = (50, 10, 80)
        template = copy.deepcopy(_BUILTIN_TEMPLATE)
        template["text_area"]["background_color"] = list(custom_color)

        with tempfile.TemporaryDirectory() as tmpdir:
            frame = os.path.join(tmpdir, "frame.jpg")
            self._make_frame(frame)
            out = os.path.join(tmpdir, "poster.jpg")
            compose_poster(frame, "center", None, out, template=template)

            with Image.open(out) as img:
                self.assertEqual(img.size, (POSTER_WIDTH, POSTER_HEIGHT))


class TestLoadFont(unittest.TestCase):
    """Tests for poster_composer._load_font()."""

    def test_load_font_returns_font_with_default_template(self):
        from video_thumbnail_creator.poster_composer import _load_font
        from video_thumbnail_creator.poster_template import _BUILTIN_TEMPLATE
        import copy
        template = copy.deepcopy(_BUILTIN_TEMPLATE)
        font = _load_font(template, 40, bold=True)
        self.assertIsNotNone(font)

    def test_load_font_regular_returns_font(self):
        from video_thumbnail_creator.poster_composer import _load_font
        from video_thumbnail_creator.poster_template import _BUILTIN_TEMPLATE
        import copy
        template = copy.deepcopy(_BUILTIN_TEMPLATE)
        font = _load_font(template, 30, bold=False)
        self.assertIsNotNone(font)

    def test_load_font_falls_back_gracefully_on_bad_font(self):
        from video_thumbnail_creator.poster_composer import _load_font
        template = {"font": {"family": "NonExistentFontXYZ123", "bold_suffix": " Bold", "regular_suffix": ""}}
        font = _load_font(template, 36, bold=True)
        self.assertIsNotNone(font)

    def test_load_font_empty_template_uses_defaults(self):
        from video_thumbnail_creator.poster_composer import _load_font
        font = _load_font({}, 36, bold=False)
        self.assertIsNotNone(font)


class TestDarkOverlay(unittest.TestCase):
    """Tests for the dark overlay (dimming) feature in compose_poster."""

    def _make_frame(self, path: str, color=(200, 200, 200)) -> None:
        img = Image.new("RGB", (1920, 1080), color=color)
        img.save(path, "JPEG")

    def test_darken_opacity_zero_no_dimming(self):
        """With darken_opacity=0.0 the poster should still be produced."""
        from video_thumbnail_creator.poster_composer import compose_poster, POSTER_WIDTH, POSTER_HEIGHT
        from video_thumbnail_creator.poster_template import _BUILTIN_TEMPLATE
        import copy

        template = copy.deepcopy(_BUILTIN_TEMPLATE)
        template["text_area"]["darken_opacity"] = 0.0

        with tempfile.TemporaryDirectory() as tmpdir:
            frame = os.path.join(tmpdir, "frame.jpg")
            self._make_frame(frame)
            out = os.path.join(tmpdir, "poster.jpg")
            compose_poster(frame, "center", "Title", out, template=template)
            with Image.open(out) as img:
                self.assertEqual(img.size, (POSTER_WIDTH, POSTER_HEIGHT))

    def test_text_area_has_solid_dark_background(self):
        """The text area should have a darkened blurred background."""
        from video_thumbnail_creator.poster_composer import compose_poster
        from video_thumbnail_creator.poster_template import _BUILTIN_TEMPLATE
        import copy

        template = copy.deepcopy(_BUILTIN_TEMPLATE)
        template["text_area"]["darken_opacity"] = 0.4

        # Use a bright source image so contrast with darkened background is obvious
        with tempfile.TemporaryDirectory() as tmpdir:
            frame = os.path.join(tmpdir, "frame.jpg")
            self._make_frame(frame, color=(220, 220, 220))
            out = os.path.join(tmpdir, "poster.jpg")
            compose_poster(frame, "center", None, out, template=template)

            separator_height = template["separator"]["height"]
            text_area_y = _BUILTIN_TEMPLATE["poster"]["image_height"] + separator_height + 20
            sample_x = _BUILTIN_TEMPLATE["poster"]["width"] // 2

            with Image.open(out) as img:
                pixel = img.getpixel((sample_x, text_area_y))

            # The pixel should be noticeably darker than the bright source (220,220,220)
            # With darken_opacity=0.4: expected ~220*(1-0.4)=132; well below 180
            for actual in pixel[:3]:
                self.assertLess(actual, 180,
                                msg="Text area pixel should be darkened below source brightness")

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