"""
贴纸引擎配置与纯逻辑测试.

覆盖 ImageStickerConfig / TextStickerConfig / _resolve_position / 常量与便捷函数.
引擎核心滤镜生成与渲染依赖 FFmpeg，由集成测试覆盖.
"""

from __future__ import annotations

import pytest
from video_processing.sticker_engine import (
    POSITION_PRESETS,
    STICKER_CATEGORIES,
    ImageStickerConfig,
    StickerEngine,
    StickerOverlayResult,
    TextStickerConfig,
    get_sticker_categories,
    parse_stickers_from_config,
)


class TestStickerConstants:
    """常量测试."""

    def test_nine_position_presets(self):
        assert len(POSITION_PRESETS) == 9
        assert "top_left" in POSITION_PRESETS
        assert "top_center" in POSITION_PRESETS
        assert "top_right" in POSITION_PRESETS
        assert "center_left" in POSITION_PRESETS
        assert "center" in POSITION_PRESETS
        assert "center_right" in POSITION_PRESETS
        assert "bottom_left" in POSITION_PRESETS
        assert "bottom_center" in POSITION_PRESETS
        assert "bottom_right" in POSITION_PRESETS

    def test_position_values_are_fractions(self):
        for name, (x, y) in POSITION_PRESETS.items():
            assert 0.0 <= x <= 1.0, f"{name} x={x} out of range"
            assert 0.0 <= y <= 1.0, f"{name} y={y} out of range"

    def test_sticker_categories(self):
        assert len(STICKER_CATEGORIES) >= 3
        for cat_id, cat_name in STICKER_CATEGORIES:
            assert isinstance(cat_id, str)
            assert isinstance(cat_name, str)
            assert len(cat_id) > 0
            assert len(cat_name) > 0


class TestImageStickerConfig:
    """图片贴纸配置."""

    def test_default_values(self):
        cfg = ImageStickerConfig()
        assert cfg.enabled is False
        assert cfg.type == "image"
        assert cfg.position == "top_right"
        assert cfg.x is None
        assert cfg.y is None
        assert cfg.x_unit == "percent"
        assert cfg.y_unit == "percent"
        assert cfg.scale == 1.0
        assert cfg.width is None
        assert cfg.height is None
        assert cfg.opacity == 1.0
        assert cfg.start_time == 0.0
        assert cfg.duration == 0.0
        assert cfg.fade_in == 0.0
        assert cfg.fade_out == 0.0
        assert cfg.z_index == 10
        assert cfg.image_url == ""
        assert cfg.preset_id == ""

    def test_custom_values(self):
        cfg = ImageStickerConfig(
            enabled=True,
            position="center",
            x=50.0,
            y=30.0,
            scale=0.5,
            opacity=0.8,
            start_time=1.0,
            duration=5.0,
            fade_in=0.5,
            fade_out=0.5,
            z_index=5,
            image_url="/tmp/sticker.png",
            preset_id="sticker_001",
        )
        assert cfg.enabled is True
        assert cfg.position == "center"
        assert cfg.x == 50.0
        assert cfg.y == 30.0
        assert cfg.scale == 0.5
        assert cfg.opacity == 0.8
        assert cfg.start_time == 1.0
        assert cfg.duration == 5.0
        assert cfg.z_index == 5
        assert cfg.image_url == "/tmp/sticker.png"


class TestTextStickerConfig:
    """文字贴纸配置."""

    def test_default_values(self):
        cfg = TextStickerConfig()
        assert cfg.enabled is False
        assert cfg.type == "text"
        assert cfg.text == ""
        assert cfg.font_size == 36
        assert cfg.font_color == "#FFFFFF"
        assert cfg.font_family == "sans"
        assert cfg.stroke_color == "#000000"
        assert cfg.stroke_width == 2
        assert cfg.shadow_color == "#000000"
        assert cfg.shadow_x == 2
        assert cfg.shadow_y == 2
        assert cfg.shadow_alpha == 0.5
        assert cfg.position == "center"
        assert cfg.z_index == 10
        assert cfg.bg_color == ""
        assert cfg.bg_padding == 8
        assert cfg.bg_alpha == 0.8
        assert cfg.bg_corner_radius == 8

    def test_custom_text_sticker(self):
        cfg = TextStickerConfig(
            enabled=True,
            text="Hello",
            font_size=48,
            font_color="#FF0000",
            position="bottom_center",
            bg_color="#000000",
            bg_padding=16,
        )
        assert cfg.enabled is True
        assert cfg.text == "Hello"
        assert cfg.font_size == 48
        assert cfg.font_color == "#FF0000"
        assert cfg.position == "bottom_center"
        assert cfg.bg_color == "#000000"
        assert cfg.bg_padding == 16


class TestStickerOverlayResult:
    """贴纸叠加结果."""

    def test_default_values(self):
        result = StickerOverlayResult(filter_str="overlay=10:20", output_label="[out]")
        assert result.filter_str == "overlay=10:20"
        assert result.output_label == "[out]"
        assert result.extra_inputs == []

    def test_with_extra_inputs(self):
        result = StickerOverlayResult(
            filter_str="overlay=0:0",
            output_label="[out]",
            extra_inputs=["/tmp/sticker.png"],
        )
        assert len(result.extra_inputs) == 1
        assert result.extra_inputs[0] == "/tmp/sticker.png"


class TestResolvePosition:
    """_resolve_position 位置解析."""

    def test_top_left_preset(self):
        cfg = ImageStickerConfig(position="top_left")
        x, y = StickerEngine._resolve_position(cfg, 1000, 500, 100, 50)
        # top_left: (0.05, 0.05) → x = 0.05*1000 - 50 = 0, y = 0.05*500 - 25 = 0
        assert x >= 0
        assert y >= 0

    def test_center_preset(self):
        cfg = ImageStickerConfig(position="center")
        x, y = StickerEngine._resolve_position(cfg, 1000, 500, 200, 100)
        # center: (0.5, 0.5) → x = 500 - 100 = 400, y = 250 - 50 = 200
        assert x == 400.0
        assert y == 200.0

    def test_bottom_right_preset(self):
        cfg = ImageStickerConfig(position="bottom_right")
        x, y = StickerEngine._resolve_position(cfg, 1000, 500, 100, 50)
        # bottom_right: (0.95, 0.95) → x = 950 - 50 = 900, y = 475 - 25 = 450
        assert x == 900.0
        assert y == 450.0

    def test_custom_percent_position(self):
        cfg = ImageStickerConfig(position="center", x=25.0, y=75.0, x_unit="percent", y_unit="percent")
        x, y = StickerEngine._resolve_position(cfg, 1000, 500, 100, 50)
        # x = 0.25*1000 - 50 = 200, y = 0.75*500 - 25 = 350
        assert x == 200.0
        assert y == 350.0

    def test_custom_pixel_position(self):
        cfg = ImageStickerConfig(position="center", x=300, y=200, x_unit="pixel", y_unit="pixel")
        x, y = StickerEngine._resolve_position(cfg, 1000, 500, 100, 50)
        # x = 300/1000*1000 - 50 = 250, y = 200/500*500 - 25 = 175
        # 等等，让我重新算：px = config.x / canvas_w = 300/1000 = 0.3
        # x = px * canvas_w - sticker_w/2 = 0.3*1000 - 50 = 300 - 50 = 250
        assert x == 250.0
        assert y == 175.0

    def test_zero_size_sticker(self):
        cfg = ImageStickerConfig(position="center")
        x, y = StickerEngine._resolve_position(cfg, 1000, 500, 0, 0)
        # 贴纸尺寸为0时，位置就是中心点
        assert x == 500.0
        assert y == 250.0

    def test_invalid_position_falls_back_to_center(self):
        cfg = ImageStickerConfig(position="invalid_position")
        x, y = StickerEngine._resolve_position(cfg, 1000, 500, 100, 50)
        # 无效位置 → 默认居中 → x = 500 - 50 = 450, y = 250 - 25 = 225
        assert x == 450.0
        assert y == 225.0

    def test_position_clamped_to_canvas(self):
        # 贴纸太大，位置被钳制
        cfg = ImageStickerConfig(position="top_left")
        x, y = StickerEngine._resolve_position(cfg, 100, 100, 200, 200)
        # 贴纸比画布还大，应该被钳制到 0
        assert x >= 0
        assert y >= 0
        assert x <= 100
        assert y <= 100

    def test_zero_canvas_handling(self):
        cfg = ImageStickerConfig(position="center", x=50, y=50, x_unit="pixel", y_unit="pixel")
        x, y = StickerEngine._resolve_position(cfg, 0, 0, 10, 10)
        # 画布为0时不应崩溃，结果被钳制到0
        assert x == 0
        assert y == 0

    def test_text_sticker_position(self):
        cfg = TextStickerConfig(position="top_right")
        x, y = StickerEngine._resolve_position(cfg, 800, 400, 100, 30)
        # top_right: (0.95, 0.05) → x = 760 - 50 = 710, 但被钳制到 canvas_w - sticker_w = 700
        # y = 20 - 15 = 5
        assert x == 700.0
        assert y == 5.0


class TestParseStickersFromConfig:
    """parse_stickers_from_config 便捷函数."""

    def test_none_returns_empty(self):
        assert parse_stickers_from_config(None) == []

    def test_empty_dict_returns_empty(self):
        assert parse_stickers_from_config({}) == []

    def test_no_stickers_key_returns_empty(self):
        assert parse_stickers_from_config({"other": "data"}) == []

    def test_stickers_list_returned(self):
        stickers = [{"type": "image", "url": "/a.png"}, {"type": "text", "text": "hi"}]
        result = parse_stickers_from_config({"stickers": stickers})
        assert result == stickers
        assert len(result) == 2

    def test_stickers_not_a_list_returns_empty(self):
        assert parse_stickers_from_config({"stickers": "not_a_list"}) == []

    def test_empty_stickers_list(self):
        assert parse_stickers_from_config({"stickers": []}) == []


class TestGetStickerCategories:
    """get_sticker_categories 便捷函数."""

    def test_returns_list_of_tuples(self):
        result = get_sticker_categories()
        assert isinstance(result, list)
        assert len(result) > 0
        for item in result:
            assert isinstance(item, tuple)
            assert len(item) == 2

    def test_matches_constant(self):
        result = get_sticker_categories()
        assert result == list(STICKER_CATEGORIES)
