"""subtitle 字幕时间轴领域模型单测."""

import pytest
from domain.subtitle import SubtitleSegment, SubtitleTimeline, SubtitleWord

# ── SubtitleWord ─────────────────────────────────────────────────────────────


class TestSubtitleWord:
    """SubtitleWord 词级字幕单元"""

    def test_basic(self):
        w = SubtitleWord(text="你好", start=1.0, end=1.5)
        assert w.text == "你好"
        assert w.start == 1.0
        assert w.end == 1.5

    def test_duration(self):
        w = SubtitleWord(text="test", start=0.0, end=2.5)
        assert w.duration == 2.5

    def test_duration_zero(self):
        w = SubtitleWord(text="x", start=5.0, end=5.0)
        assert w.duration == 0.0

    def test_duration_negative_becomes_zero(self):
        w = SubtitleWord(text="x", start=3.0, end=2.0)
        assert w.duration == 0.0


# ── SubtitleSegment ──────────────────────────────────────────────────────────


class TestSubtitleSegment:
    """SubtitleSegment 字幕片段"""

    def test_basic(self):
        s = SubtitleSegment(text="你好世界", start=0.0, end=2.0)
        assert s.text == "你好世界"
        assert s.start == 0.0
        assert s.end == 2.0
        assert s.words == []

    def test_with_words(self):
        words = [
            SubtitleWord("你好", 0.0, 0.5),
            SubtitleWord("世界", 0.5, 1.0),
        ]
        s = SubtitleSegment(text="你好世界", start=0.0, end=1.0, words=words)
        assert len(s.words) == 2
        assert s.words[0].text == "你好"

    def test_duration(self):
        s = SubtitleSegment(text="test", start=1.5, end=3.5)
        assert s.duration == 2.0

    def test_duration_negative_becomes_zero(self):
        s = SubtitleSegment(text="test", start=5.0, end=3.0)
        assert s.duration == 0.0

    def test_char_count(self):
        s = SubtitleSegment(text="你好世界", start=0, end=1)
        assert s.char_count == 4

    def test_char_count_empty(self):
        s = SubtitleSegment(text="", start=0, end=1)
        assert s.char_count == 0

    def test_char_count_mixed(self):
        s = SubtitleSegment(text="Hello 世界", start=0, end=1)
        assert s.char_count == 8  # H-e-l-l-o- -世-界


# ── SubtitleTimeline 基础 ────────────────────────────────────────────────────


class TestSubtitleTimelineBasics:
    """SubtitleTimeline 基础属性"""

    def test_defaults(self):
        tl = SubtitleTimeline()
        assert tl.segments == []
        assert tl.language == "zh"
        assert tl.total_duration == 0.0

    def test_custom_language(self):
        tl = SubtitleTimeline(language="en")
        assert tl.language == "en"

    def test_segment_count(self):
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment("a", 0, 1),
                SubtitleSegment("b", 1, 2),
            ]
        )
        assert tl.segment_count == 2

    def test_segment_count_empty(self):
        tl = SubtitleTimeline()
        assert tl.segment_count == 0

    def test_total_chars(self):
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment("你好", 0, 1),
                SubtitleSegment("世界", 1, 2),
            ]
        )
        assert tl.total_chars == 4

    def test_total_chars_empty(self):
        tl = SubtitleTimeline()
        assert tl.total_chars == 0


# ── merge_short_segments ─────────────────────────────────────────────────────


class TestMergeShortSegments:
    """merge_short_segments 合并过短片段"""

    def test_empty_timeline(self):
        tl = SubtitleTimeline()
        result = tl.merge_short_segments()
        assert result.segment_count == 0

    def test_single_segment(self):
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment("短", 0, 1),
            ]
        )
        result = tl.merge_short_segments(min_chars=8)
        assert result.segment_count == 1
        assert result.segments[0].text == "短"

    def test_two_short_segments_merged(self):
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment("你好", 0, 1),  # 2
                SubtitleSegment("世界", 1, 2),  # 2
            ]
        )
        result = tl.merge_short_segments(min_chars=3)
        assert result.segment_count == 1
        assert result.segments[0].text == "你好世界"
        assert result.segments[0].start == 0.0
        assert result.segments[0].end == 2.0

    def test_multiple_short_merged(self):
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment("一", 0, 0.5),  # 1
                SubtitleSegment("二", 0.5, 1.0),  # 1
                SubtitleSegment("三", 1.0, 1.5),  # 1
                SubtitleSegment("四", 1.5, 2.0),  # 1
                SubtitleSegment("五", 2.0, 2.5),  # 1
                SubtitleSegment("六七八", 2.5, 3.5),  # 3
                SubtitleSegment("八九十", 3.5, 4.5),  # 3
            ]
        )
        result = tl.merge_short_segments(min_chars=5)
        # 一二三四五 5个=5 → 合并为1段
        # 六七八+八九十 3+3=6 → 合并为1段
        assert result.segment_count == 2
        assert result.segments[0].text == "一二三四五"
        assert result.segments[1].text == "六七八八九十"

    def test_long_segment_stays_alone(self):
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment("这是一段很长的字幕内容", 0, 2),  # 11
                SubtitleSegment("短", 2, 2.5),  # 1
                SubtitleSegment("语", 2.5, 3.0),  # 1
            ]
        )
        result = tl.merge_short_segments(min_chars=8)
        # 第一段11字>=8，单独输出；后两段加起来2字<8，合并到上一段
        assert result.segment_count == 1
        assert result.segments[0].text == "这是一段很长的字幕内容短语"

    def test_tail_short_merged_with_previous(self):
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment("一二三四五六七八", 0, 2),  # 8
                SubtitleSegment("尾", 2, 2.5),  # 1，太短了
            ]
        )
        result = tl.merge_short_segments(min_chars=5)
        assert result.segment_count == 1
        assert result.segments[0].text == "一二三四五六七八尾"

    def test_preserves_language_and_duration(self):
        tl = SubtitleTimeline(
            segments=[SubtitleSegment("a", 0, 1)],
            language="en",
            total_duration=10.0,
        )
        result = tl.merge_short_segments()
        assert result.language == "en"
        assert result.total_duration == 10.0

    def test_default_min_chars_is_8(self):
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment("一二三四五", 0, 1),  # 5 < 8
                SubtitleSegment("六七八", 1, 2),  # 3 → 5+3=8
            ]
        )
        result = tl.merge_short_segments()
        assert result.segment_count == 1

    def test_merges_words(self):
        words1 = [SubtitleWord("你", 0.0, 0.3), SubtitleWord("好", 0.3, 0.6)]
        words2 = [SubtitleWord("世", 1.0, 1.3), SubtitleWord("界", 1.3, 1.6)]
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment("你好", 0.0, 0.6, words=words1),
                SubtitleSegment("世界", 1.0, 1.6, words=words2),
            ]
        )
        result = tl.merge_short_segments(min_chars=3)
        assert result.segment_count == 1
        assert len(result.segments[0].words) == 4
        assert result.segments[0].words[0].text == "你"
        assert result.segments[0].words[3].text == "界"

    def test_does_not_mutate_original(self):
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment("a", 0, 1),
                SubtitleSegment("b", 1, 2),
            ]
        )
        original_count = tl.segment_count
        tl.merge_short_segments(min_chars=5)
        assert tl.segment_count == original_count


# ── split_long_segments ──────────────────────────────────────────────────────


class TestSplitLongSegments:
    """split_long_segments 拆分过长片段"""

    def test_short_segment_no_split(self):
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment("短文本", 0, 1),
            ]
        )
        result = tl.split_long_segments(max_chars=20)
        assert result.segment_count == 1
        assert result.segments[0].text == "短文本"

    def test_empty_timeline(self):
        tl = SubtitleTimeline()
        result = tl.split_long_segments()
        assert result.segment_count == 0

    def test_split_by_sentence_end(self):
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment(
                    "这是第一句话。这是第二句话。这是第三句话。",
                    start=0.0,
                    end=9.0,
                ),
            ]
        )
        result = tl.split_long_segments(max_chars=10)
        assert result.segment_count >= 2
        # 第一句应该是完整的
        assert result.segments[0].text.endswith("。")

    def test_split_preserves_total_text(self):
        original = "这是第一句话。这是第二句话。这是第三句话，很长的一句话。"
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment(original, start=0.0, end=10.0),
            ]
        )
        result = tl.split_long_segments(max_chars=8)
        # 拆分后所有片段拼起来应该等于原文
        combined = "".join(s.text for s in result.segments)
        assert combined == original

    def test_split_time_proportional(self):
        text = "一二三四五六七八九十。一二三四五六七八九十。"
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment(text=text, start=0.0, end=10.0),
            ]
        )
        result = tl.split_long_segments(max_chars=12)
        assert result.segment_count >= 2
        # 第一段结束时间应该早于总时长
        assert result.segments[0].end < 10.0
        # 最后一段结束应该等于原结束时间
        assert abs(result.segments[-1].end - 10.0) < 0.01

    def test_no_punctuation_hard_split(self):
        text = "一二三四五六七八九十一二三四五六七八九十一二三四五"
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment(text=text, start=0.0, end=10.0),
            ]
        )
        result = tl.split_long_segments(max_chars=10)
        assert result.segment_count >= 3
        combined = "".join(s.text for s in result.segments)
        assert combined == text

    def test_multiple_mixed_segments(self):
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment("短", 0, 1),
                SubtitleSegment("这是一段非常非常长的字幕文本内容需要拆分", 1, 5),
                SubtitleSegment("短的", 5, 6),
            ]
        )
        result = tl.split_long_segments(max_chars=10)
        # 第一个和第三个保持不变，中间被拆分
        assert result.segment_count > 3
        assert result.segments[0].text == "短"
        assert result.segments[-1].text == "短的"

    def test_preserves_language_and_total_duration(self):
        tl = SubtitleTimeline(
            segments=[SubtitleSegment("a" * 30, 0, 10)],
            language="ja",
            total_duration=20.0,
        )
        result = tl.split_long_segments(max_chars=10)
        assert result.language == "ja"
        assert result.total_duration == 20.0

    def test_default_max_chars_is_20(self):
        text = "一" * 25
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment(text=text, start=0, end=5),
            ]
        )
        result = tl.split_long_segments()
        assert result.segment_count >= 2

    def test_split_with_words(self):
        words = [SubtitleWord(f"w{i}", i * 0.5, i * 0.5 + 0.4) for i in range(20)]
        text = "".join(w.text for w in words)
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment(text=text, start=0.0, end=10.0, words=words),
            ]
        )
        result = tl.split_long_segments(max_chars=10)
        assert result.segment_count >= 2
        # 所有片段的词数之和应该等于原词数
        total_words = sum(len(s.words) for s in result.segments)
        assert total_words <= len(words) + 1  # 可能有边界误差

    def test_does_not_mutate_original(self):
        tl = SubtitleTimeline(
            segments=[
                SubtitleSegment("a" * 30, 0, 10),
            ]
        )
        original_count = tl.segment_count
        tl.split_long_segments(max_chars=10)
        assert tl.segment_count == original_count


# ── _split_text_by_punctuation 静态方法 ─────────────────────────────────────


class TestSplitTextByPunctuation:
    """_split_text_by_punctuation 静态方法"""

    def test_short_text_no_split(self):
        result = SubtitleTimeline._split_text_by_punctuation("短文本", max_chars=20)
        assert len(result) == 1
        assert result[0] == "短文本"

    def test_sentence_end_punctuation_split(self):
        result = SubtitleTimeline._split_text_by_punctuation(
            "第一句。第二句。第三句。",
            max_chars=5,
        )
        assert len(result) >= 2
        assert result[0] == "第一句。"

    def test_clause_pause_punctuation(self):
        result = SubtitleTimeline._split_text_by_punctuation(
            "今天天气很好，阳光明媚，适合出去玩。",
            max_chars=8,
        )
        assert len(result) >= 2

    def test_exclamation_mark(self):
        result = SubtitleTimeline._split_text_by_punctuation(
            "太精彩了！真的很棒！",
            max_chars=5,
        )
        assert len(result) >= 2

    def test_question_mark(self):
        result = SubtitleTimeline._split_text_by_punctuation(
            "你是谁？从哪里来？",
            max_chars=5,
        )
        assert len(result) >= 2

    def test_english_punctuation(self):
        result = SubtitleTimeline._split_text_by_punctuation(
            "Hello, world! How are you?",
            max_chars=10,
        )
        assert len(result) >= 2

    def test_no_punctuation_hard_split(self):
        text = "一" * 25
        result = SubtitleTimeline._split_text_by_punctuation(text, max_chars=10)
        assert len(result) >= 3
        assert "".join(result) == text

    def test_empty_string(self):
        result = SubtitleTimeline._split_text_by_punctuation("", max_chars=10)
        assert len(result) == 0 or (len(result) == 1 and result[0] == "")

    def test_semicolon_colon(self):
        result = SubtitleTimeline._split_text_by_punctuation(
            "注意事项：第一，要认真；第二，要仔细。",
            max_chars=8,
        )
        assert len(result) >= 2


# ── _merge_segments 静态方法 ────────────────────────────────────────────────


class TestMergeSegmentsStatic:
    """_merge_segments 静态方法"""

    def test_empty_list(self):
        result = SubtitleTimeline._merge_segments([])
        assert result.text == ""
        assert result.start == 0
        assert result.end == 0

    def test_single_segment(self):
        seg = SubtitleSegment("hello", 1.0, 2.0)
        result = SubtitleTimeline._merge_segments([seg])
        assert result.text == "hello"
        assert result.start == 1.0
        assert result.end == 2.0

    def test_two_segments(self):
        s1 = SubtitleSegment("你好", 0.0, 1.0)
        s2 = SubtitleSegment("世界", 1.0, 2.0)
        result = SubtitleTimeline._merge_segments([s1, s2])
        assert result.text == "你好世界"
        assert result.start == 0.0
        assert result.end == 2.0

    def test_merges_words(self):
        w1 = [SubtitleWord("你", 0, 0.5)]
        w2 = [SubtitleWord("好", 0.5, 1.0)]
        s1 = SubtitleSegment("你", 0, 0.5, words=w1)
        s2 = SubtitleSegment("好", 0.5, 1.0, words=w2)
        result = SubtitleTimeline._merge_segments([s1, s2])
        assert len(result.words) == 2
        assert result.words[0].text == "你"
        assert result.words[1].text == "好"


# ── 端到端：先合并再拆分 ────────────────────────────────────────────────────


class TestMergeAndSplit:
    """合并和拆分组合使用"""

    def test_merge_then_split_roundtrip(self):
        # 很多短句先合并，再按合理长度拆分
        segments = [
            SubtitleSegment("你好", 0, 0.5),
            SubtitleSegment("我是小明", 0.5, 1.5),
            SubtitleSegment("今天天气真好。", 1.5, 3.0),
            SubtitleSegment("我们出去玩吧。", 3.0, 5.0),
        ]
        tl = SubtitleTimeline(segments=segments)
        merged = tl.merge_short_segments(min_chars=5)
        split = merged.split_long_segments(max_chars=15)
        # 结果应该合理（不保证完全一样，但文本应该完整）
        original_text = "".join(s.text for s in segments)
        result_text = "".join(s.text for s in split.segments)
        assert original_text == result_text
