"""watermark_config 领域模型单测."""

from __future__ import annotations

import pytest

from packages.domain.watermark_config import (
    DEFAULT_FONT_COLOR,
    DEFAULT_FONT_SIZE,
    DEFAULT_MODE,
    DEFAULT_OPACITY,
    DEFAULT_POSITION,
    DEFAULT_SCALE,
    VALID_POSITIONS,
    WATERMARK_POSITIONS,
    WatermarkConfig,
    build_image_watermark_filter,
    build_text_watermark_filter,
    calc_position,
    calc_scroll_x,
    get_position_display_name,
    get_position_names,
)

# ── 常量测试 ────────────────────────────────────────────────────────────────


class TestConstants:
    def test_nine_positions(self):
        assert len(WATERMARK_POSITIONS) == 9

    def test_all_position_keys_valid(self):
        for key in WATERMARK_POSITIONS:
            assert key in VALID_POSITIONS

    def test_valid_positions_match(self):
        assert set(WATERMARK_POSITIONS.keys()) == VALID_POSITIONS

    def test_default_values(self):
        assert DEFAULT_POSITION == "bottom_right"
        assert DEFAULT_MODE == "text"
        assert DEFAULT_SCALE == 0.2
        assert DEFAULT_OPACITY == 0.8
        assert DEFAULT_FONT_SIZE == 24
        assert DEFAULT_FONT_COLOR == "white"


# ── WatermarkConfig.from_dict 测试 ─────────────────────────────────────────


class TestWatermarkConfigFromDict:
    def test_none_returns_none(self):
        assert WatermarkConfig.from_dict(None) is None

    def test_empty_dict_returns_none(self):
        assert WatermarkConfig.from_dict({}) is None

    def test_disabled_returns_none(self):
        assert WatermarkConfig.from_dict({"enabled": False}) is None

    def test_text_mode_basic(self):
        cfg = WatermarkConfig.from_dict({"enabled": True, "mode": "text", "text": "hello"})
        assert cfg is not None
        assert cfg.mode == "text"
        assert cfg.text == "hello"
        assert cfg.position == DEFAULT_POSITION

    def test_image_mode_basic(self):
        cfg = WatermarkConfig.from_dict({"enabled": True, "mode": "image", "image_path": "/tmp/wm.png"})
        assert cfg is not None
        assert cfg.mode == "image"
        assert cfg.image_path == "/tmp/wm.png"

    def test_image_mode_accepts_image_key(self):
        cfg = WatermarkConfig.from_dict({"enabled": True, "mode": "image", "image": "/tmp/wm.png"})
        assert cfg is not None
        assert cfg.image_path == "/tmp/wm.png"

    def test_image_mode_missing_path_returns_none(self):
        assert WatermarkConfig.from_dict({"enabled": True, "mode": "image"}) is None

    def test_text_mode_missing_text_returns_none(self):
        assert WatermarkConfig.from_dict({"enabled": True, "mode": "text"}) is None

    def test_text_mode_empty_text_returns_none(self):
        assert WatermarkConfig.from_dict({"enabled": True, "mode": "text", "text": ""}) is None

    def test_invalid_position_defaults(self):
        cfg = WatermarkConfig.from_dict({"enabled": True, "mode": "text", "text": "hi", "position": "invalid"})
        assert cfg.position == DEFAULT_POSITION

    def test_custom_all_params(self):
        cfg = WatermarkConfig.from_dict(
            {
                "enabled": True,
                "mode": "text",
                "text": "测试水印",
                "position": "top_left",
                "font_size": 32,
                "font_color": "red",
                "opacity": 0.5,
                "margin_x": 30,
                "margin_y": 40,
                "scroll": True,
                "scroll_speed": 100,
            }
        )
        assert cfg is not None
        assert cfg.text == "测试水印"
        assert cfg.position == "top_left"
        assert cfg.font_size == 32
        assert cfg.font_color == "red"
        assert cfg.opacity == 0.5
        assert cfg.margin_x == 30
        assert cfg.margin_y == 40
        assert cfg.scroll is True
        assert cfg.scroll_speed == 100

    def test_default_mode_is_text(self):
        cfg = WatermarkConfig.from_dict({"enabled": True, "text": "hi"})
        assert cfg is not None
        assert cfg.mode == "text"


# ── WatermarkConfig.validate 测试 ──────────────────────────────────────────


class TestWatermarkConfigValidate:
    def test_valid_text_config(self):
        cfg = WatermarkConfig(mode="text", text="hello")
        ok, msg = cfg.validate()
        assert ok is True
        assert msg == ""

    def test_valid_image_config(self):
        cfg = WatermarkConfig(mode="image", image_path="/tmp/wm.png", scale=0.3)
        ok, msg = cfg.validate()
        assert ok is True

    def test_invalid_position(self):
        cfg = WatermarkConfig(mode="text", text="hi", position="nowhere")
        ok, msg = cfg.validate()
        assert ok is False
        assert "位置" in msg

    def test_opacity_negative(self):
        cfg = WatermarkConfig(mode="text", text="hi", opacity=-0.1)
        ok, msg = cfg.validate()
        assert ok is False
        assert "透明度" in msg

    def test_opacity_over_one(self):
        cfg = WatermarkConfig(mode="text", text="hi", opacity=1.5)
        ok, msg = cfg.validate()
        assert ok is False

    def test_opacity_boundary_zero(self):
        cfg = WatermarkConfig(mode="text", text="hi", opacity=0.0)
        ok, _ = cfg.validate()
        assert ok is True

    def test_opacity_boundary_one(self):
        cfg = WatermarkConfig(mode="text", text="hi", opacity=1.0)
        ok, _ = cfg.validate()
        assert ok is True

    def test_image_missing_path(self):
        cfg = WatermarkConfig(mode="image")
        ok, msg = cfg.validate()
        assert ok is False
        assert "图片路径" in msg

    def test_image_scale_too_small(self):
        cfg = WatermarkConfig(mode="image", image_path="/a.png", scale=0.001)
        ok, msg = cfg.validate()
        assert ok is False
        assert "缩放比例" in msg

    def test_image_scale_too_large(self):
        cfg = WatermarkConfig(mode="image", image_path="/a.png", scale=2.0)
        ok, msg = cfg.validate()
        assert ok is False

    def test_image_scale_boundary_low(self):
        cfg = WatermarkConfig(mode="image", image_path="/a.png", scale=0.01)
        ok, _ = cfg.validate()
        assert ok is True

    def test_image_scale_boundary_high(self):
        cfg = WatermarkConfig(mode="image", image_path="/a.png", scale=1.0)
        ok, _ = cfg.validate()
        assert ok is True

    def test_text_missing_content(self):
        cfg = WatermarkConfig(mode="text", text="")
        ok, msg = cfg.validate()
        assert ok is False
        assert "文字内容" in msg

    def test_text_font_size_zero(self):
        cfg = WatermarkConfig(mode="text", text="hi", font_size=0)
        ok, msg = cfg.validate()
        assert ok is False
        assert "字体大小" in msg

    def test_text_font_size_negative(self):
        cfg = WatermarkConfig(mode="text", text="hi", font_size=-5)
        ok, msg = cfg.validate()
        assert ok is False

    def test_unknown_mode(self):
        cfg = WatermarkConfig(mode="video")
        ok, msg = cfg.validate()
        assert ok is False
        assert "模式" in msg


# ── has_effect 测试 ────────────────────────────────────────────────────────


class TestHasEffect:
    def test_text_with_content_has_effect(self):
        cfg = WatermarkConfig(mode="text", text="hello")
        assert cfg.has_effect() is True

    def test_text_empty_no_effect(self):
        cfg = WatermarkConfig(mode="text", text="")
        assert cfg.has_effect() is False

    def test_text_zero_opacity_no_effect(self):
        cfg = WatermarkConfig(mode="text", text="hello", opacity=0.0)
        assert cfg.has_effect() is False

    def test_image_with_path_has_effect(self):
        cfg = WatermarkConfig(mode="image", image_path="/a.png")
        assert cfg.has_effect() is True

    def test_image_no_path_no_effect(self):
        cfg = WatermarkConfig(mode="image")
        assert cfg.has_effect() is False

    def test_unknown_mode_no_effect(self):
        cfg = WatermarkConfig(mode="unknown")
        assert cfg.has_effect() is False


# ── calc_position 测试 ─────────────────────────────────────────────────────


class TestCalcPosition:
    def test_top_left(self):
        x, y = calc_position("top_left", 1000, 2000, 100, 50, 10, 20)
        assert (x, y) == (10, 20)

    def test_top_center(self):
        x, y = calc_position("top_center", 1000, 2000, 100, 50, 10, 20)
        assert (x, y) == (450, 20)

    def test_top_right(self):
        x, y = calc_position("top_right", 1000, 2000, 100, 50, 10, 20)
        assert (x, y) == (890, 20)

    def test_center_left(self):
        x, y = calc_position("center_left", 1000, 2000, 100, 50, 10, 20)
        assert (x, y) == (10, 975)

    def test_center(self):
        x, y = calc_position("center", 1000, 2000, 100, 50, 10, 20)
        assert (x, y) == (450, 975)

    def test_center_right(self):
        x, y = calc_position("center_right", 1000, 2000, 100, 50, 10, 20)
        assert (x, y) == (890, 975)

    def test_bottom_left(self):
        x, y = calc_position("bottom_left", 1000, 2000, 100, 50, 10, 20)
        assert (x, y) == (10, 1930)

    def test_bottom_center(self):
        x, y = calc_position("bottom_center", 1000, 2000, 100, 50, 10, 20)
        assert (x, y) == (450, 1930)

    def test_bottom_right(self):
        x, y = calc_position("bottom_right", 1000, 2000, 100, 50, 10, 20)
        assert (x, y) == (890, 1930)

    def test_unknown_position_defaults_bottom_right(self):
        x, y = calc_position("invalid", 1000, 2000, 100, 50, 10, 20)
        assert (x, y) == (890, 1930)

    def test_zero_margin(self):
        x, y = calc_position("top_left", 1000, 2000, 100, 50, 0, 0)
        assert (x, y) == (0, 0)

    def test_small_output(self):
        x, y = calc_position("center", 100, 100, 50, 30, 5, 5)
        assert (x, y) == (25, 35)


# ── calc_scroll_x 测试 ─────────────────────────────────────────────────────


class TestCalcScrollX:
    def test_returns_string_expression(self):
        result = calc_scroll_x("bottom", 1000, 200, 50)
        assert isinstance(result, str)

    def test_contains_mod_function(self):
        result = calc_scroll_x("bottom", 1000, 200, 50)
        assert "mod" in result

    def test_contains_speed_and_width(self):
        result = calc_scroll_x("bottom", 1080, 300, 60)
        assert "1080" in result
        assert "60" in result
        assert "300" in result


# ── build_image_watermark_filter 测试 ──────────────────────────────────────


class TestBuildImageWatermarkFilter:
    def test_returns_tuple(self):
        cfg = WatermarkConfig(mode="image", image_path="/wm.png")
        result = build_image_watermark_filter("[in]", "/wm.png", 1080, 1920, "[out]", cfg)
        assert isinstance(result, tuple)
        assert len(result) == 2

    def test_filter_contains_overlay(self):
        cfg = WatermarkConfig(mode="image", image_path="/wm.png")
        filter_str, inputs = build_image_watermark_filter("[in]", "/wm.png", 1080, 1920, "[out]", cfg)
        assert "overlay" in filter_str

    def test_filter_contains_scale(self):
        cfg = WatermarkConfig(mode="image", image_path="/wm.png", scale=0.5)
        filter_str, _ = build_image_watermark_filter("[in]", "/wm.png", 1080, 1920, "[out]", cfg)
        assert "scale=" in filter_str

    def test_full_opacity_no_alpha_filter(self):
        cfg = WatermarkConfig(mode="image", image_path="/wm.png", opacity=1.0)
        filter_str, _ = build_image_watermark_filter("[in]", "/wm.png", 1080, 1920, "[out]", cfg)
        assert "colorchannelmixer" not in filter_str

    def test_partial_opacity_has_alpha_filter(self):
        cfg = WatermarkConfig(mode="image", image_path="/wm.png", opacity=0.5)
        filter_str, _ = build_image_watermark_filter("[in]", "/wm.png", 1080, 1920, "[out]", cfg)
        assert "colorchannelmixer" in filter_str
        assert "aa=0.5" in filter_str

    def test_input_args_contains_image_path(self):
        cfg = WatermarkConfig(mode="image", image_path="/path/to/wm.png")
        _, inputs = build_image_watermark_filter("[in]", "/path/to/wm.png", 1080, 1920, "[out]", cfg)
        assert inputs == ["-i", "/path/to/wm.png"]

    def test_scroll_mode_has_t_variable(self):
        cfg = WatermarkConfig(mode="image", image_path="/wm.png", scroll=True, scroll_speed=50)
        filter_str, _ = build_image_watermark_filter("[in]", "/wm.png", 1080, 1920, "[out]", cfg)
        assert "t" in filter_str

    def test_output_label_appears(self):
        cfg = WatermarkConfig(mode="image", image_path="/wm.png")
        filter_str, _ = build_image_watermark_filter("[in]", "/wm.png", 1080, 1920, "[final]", cfg)
        assert "[final]" in filter_str


# ── build_text_watermark_filter 测试 ───────────────────────────────────────


class TestBuildTextWatermarkFilter:
    def test_returns_string(self):
        cfg = WatermarkConfig(mode="text", text="hello")
        result = build_text_watermark_filter("[in]", "[out]", cfg, 1080, 1920)
        assert isinstance(result, str)

    def test_contains_drawtext(self):
        cfg = WatermarkConfig(mode="text", text="hello")
        result = build_text_watermark_filter("[in]", "[out]", cfg, 1080, 1920)
        assert "drawtext=" in result

    def test_contains_text_content(self):
        cfg = WatermarkConfig(mode="text", text="watermark_test")
        result = build_text_watermark_filter("[in]", "[out]", cfg, 1080, 1920)
        assert "watermark_test" in result

    def test_contains_font_size(self):
        cfg = WatermarkConfig(mode="text", text="hi", font_size=48)
        result = build_text_watermark_filter("[in]", "[out]", cfg, 1080, 1920)
        assert "fontsize=48" in result

    def test_contains_font_color(self):
        cfg = WatermarkConfig(mode="text", text="hi", font_color="red")
        result = build_text_watermark_filter("[in]", "[out]", cfg, 1080, 1920)
        assert "fontcolor=red" in result

    def test_font_path_included_when_set(self):
        cfg = WatermarkConfig(mode="text", text="hi", font_path="/fonts/a.ttf")
        result = build_text_watermark_filter("[in]", "[out]", cfg, 1080, 1920)
        assert "fontfile=" in result
        assert "a.ttf" in result

    def test_font_path_not_included_when_empty(self):
        cfg = WatermarkConfig(mode="text", text="hi")
        result = build_text_watermark_filter("[in]", "[out]", cfg, 1080, 1920)
        assert "fontfile=" not in result

    def test_scroll_mode_has_t_variable(self):
        cfg = WatermarkConfig(mode="text", text="hello", scroll=True, scroll_speed=30)
        result = build_text_watermark_filter("[in]", "[out]", cfg, 1080, 1920)
        assert "t" in result

    def test_no_scroll_uses_fixed_position(self):
        cfg = WatermarkConfig(mode="text", text="hello", scroll=False)
        result = build_text_watermark_filter("[in]", "[out]", cfg, 1080, 1920)
        assert "x=" in result
        # 非滚动模式 x= 后面应该是数字，不是表达式
        # 找 x= 后的第一个字符
        import re

        match = re.search(r"x=(\d+)", result)
        assert match is not None

    def test_output_label_appears(self):
        cfg = WatermarkConfig(mode="text", text="hi")
        result = build_text_watermark_filter("[in]", "[text_out]", cfg, 1080, 1920)
        assert "[text_out]" in result

    def test_special_chars_escaped(self):
        cfg = WatermarkConfig(mode="text", text="hello:world")
        result = build_text_watermark_filter("[in]", "[out]", cfg, 1080, 1920)
        # 冒号应该被转义
        assert "hello\\:world" in result or "hello\\\\\\:world" in result or "hello\\:" in result


# ── 工具函数测试 ────────────────────────────────────────────────────────────


class TestUtils:
    def test_get_position_names_returns_nine(self):
        names = get_position_names()
        assert len(names) == 9

    def test_get_position_names_all_valid(self):
        names = get_position_names()
        for name in names:
            assert name in VALID_POSITIONS

    def test_get_position_display_name_valid(self):
        assert get_position_display_name("top_left") == "左上"
        assert get_position_display_name("bottom_right") == "右下"

    def test_get_position_display_name_invalid(self):
        assert get_position_display_name("invalid") == "invalid"
