"""查重域模型单元测试。

覆盖：
- DuplicationRecord.create() 工厂方法及验证
- DuplicationRecord 状态转换（mark_processing / mark_completed / mark_failed）
- DuplicationRecord.can_retry() / reset_for_retry()
- DuplicateSegment.create() 工厂方法及验证
"""

from __future__ import annotations

import pytest

from packages.domain.duplication import DuplicateSegment, DuplicationRecord


class TestDuplicationRecordCreate:
    """DuplicationRecord.create() 工厂方法测试。"""

    def test_create_success(self):
        record = DuplicationRecord.create(
            user_id="user-1",
            filename="test.mp4",
            file_size=1024,
            storage_key="oss/key/test.mp4",
            duration_seconds=30.0,
        )
        assert record.user_id == "user-1"
        assert record.filename == "test.mp4"
        assert record.file_size == 1024
        assert record.storage_key == "oss/key/test.mp4"
        assert record.duration_seconds == 30.0
        assert record.status == "pending"
        assert record.duplicate_rate is None
        assert record.duplicate_count == 0
        assert record.error_message == ""
        assert record.segments == []
        assert record.video_fingerprint is None
        assert record.id  # 自动生成 ID

    def test_create_with_default_duration(self):
        record = DuplicationRecord.create(
            user_id="user-1",
            filename="test.mp4",
            file_size=1024,
            storage_key="oss/key",
        )
        assert record.duration_seconds == 0.0

    def test_create_empty_user_id_raises(self):
        with pytest.raises(ValueError, match="user_id"):
            DuplicationRecord.create(
                user_id="",
                filename="test.mp4",
                file_size=1024,
                storage_key="oss/key",
            )

    def test_create_whitespace_user_id_raises(self):
        with pytest.raises(ValueError, match="user_id"):
            DuplicationRecord.create(
                user_id="   ",
                filename="test.mp4",
                file_size=1024,
                storage_key="oss/key",
            )

    def test_create_empty_filename_raises(self):
        with pytest.raises(ValueError, match="filename"):
            DuplicationRecord.create(
                user_id="user-1",
                filename="",
                file_size=1024,
                storage_key="oss/key",
            )

    def test_create_zero_file_size_raises(self):
        with pytest.raises(ValueError, match="file_size"):
            DuplicationRecord.create(
                user_id="user-1",
                filename="test.mp4",
                file_size=0,
                storage_key="oss/key",
            )

    def test_create_negative_file_size_raises(self):
        with pytest.raises(ValueError, match="file_size"):
            DuplicationRecord.create(
                user_id="user-1",
                filename="test.mp4",
                file_size=-100,
                storage_key="oss/key",
            )


class TestDuplicationRecordStateTransitions:
    """状态转换测试。"""

    @pytest.fixture
    def record(self):
        return DuplicationRecord.create(
            user_id="user-1",
            filename="test.mp4",
            file_size=1024,
            storage_key="oss/key",
        )

    def test_mark_processing(self, record):
        record.mark_processing()
        assert record.status == "processing"

    def test_mark_completed_success(self, record):
        record.mark_processing()
        segments = [
            DuplicateSegment.create(
                source_start=0.0,
                source_end=5.0,
                matched_video_id="vid-1",
                matched_video_name="existing.mp4",
                matched_start=0.0,
                matched_end=5.0,
                similarity=92.5,
            )
        ]
        record.mark_completed(duplicate_rate=15.0, duplicate_count=1, segments=segments)
        assert record.status == "completed"
        assert record.duplicate_rate == 15.0
        assert record.duplicate_count == 1
        assert len(record.segments) == 1

    def test_mark_completed_invalid_rate_raises(self, record):
        record.mark_processing()
        with pytest.raises(ValueError, match="duplicate_rate"):
            record.mark_completed(duplicate_rate=101.0, duplicate_count=0, segments=[])

    def test_mark_completed_negative_rate_raises(self, record):
        record.mark_processing()
        with pytest.raises(ValueError, match="duplicate_rate"):
            record.mark_completed(duplicate_rate=-1.0, duplicate_count=0, segments=[])

    def test_mark_failed(self, record):
        record.mark_processing()
        record.mark_failed("处理超时")
        assert record.status == "failed"
        assert record.error_message == "处理超时"


class TestDuplicationRecordRetry:
    """can_retry() 和 reset_for_retry() 测试。"""

    @pytest.fixture
    def record(self):
        return DuplicationRecord.create(
            user_id="user-1",
            filename="test.mp4",
            file_size=1024,
            storage_key="oss/key",
        )

    def test_mark_failed_sets_status_and_error(self, record):
        record.mark_processing()
        record.mark_failed("处理失败")
        assert record.status == "failed"
        assert record.error_message == "处理失败"

    def test_mark_failed_updates_timestamp(self, record):
        old_updated = record.updated_at
        record.mark_processing()
        record.mark_failed("错误")
        assert record.updated_at >= old_updated

    def test_failed_record_preserves_result_fields(self, record):
        """mark_failed 不改变 duplicate_rate 等结果字段（由 use case 层重置）。"""
        record.mark_processing()
        record.mark_completed(duplicate_rate=10.0, duplicate_count=1, segments=[])
        record.mark_failed("重试失败")
        assert record.status == "failed"
        assert record.error_message == "重试失败"
        assert record.duplicate_rate == 10.0


class TestDuplicateSegmentCreate:
    """DuplicateSegment.create() 工厂方法测试。"""

    def test_create_success(self):
        seg = DuplicateSegment.create(
            source_start=1.0,
            source_end=5.0,
            matched_video_id="vid-1",
            matched_video_name="existing.mp4",
            matched_start=2.0,
            matched_end=6.0,
            similarity=85.5,
        )
        assert seg.source_start == 1.0
        assert seg.source_end == 5.0
        assert seg.matched_video_id == "vid-1"
        assert seg.matched_video_name == "existing.mp4"
        assert seg.matched_start == 2.0
        assert seg.matched_end == 6.0
        assert seg.similarity == 85.5
        assert seg.id  # 自动生成 ID

    def test_create_negative_source_start_raises(self):
        with pytest.raises(ValueError, match="invalid source segment range"):
            DuplicateSegment.create(
                source_start=-1.0,
                source_end=5.0,
                matched_video_id="vid-1",
                matched_video_name="v.mp4",
                matched_start=0.0,
                matched_end=5.0,
                similarity=80.0,
            )

    def test_create_source_end_le_start_raises(self):
        with pytest.raises(ValueError, match="invalid source segment range"):
            DuplicateSegment.create(
                source_start=5.0,
                source_end=5.0,
                matched_video_id="vid-1",
                matched_video_name="v.mp4",
                matched_start=0.0,
                matched_end=5.0,
                similarity=80.0,
            )

    def test_create_negative_matched_start_raises(self):
        with pytest.raises(ValueError, match="invalid matched segment range"):
            DuplicateSegment.create(
                source_start=0.0,
                source_end=5.0,
                matched_video_id="vid-1",
                matched_video_name="v.mp4",
                matched_start=-1.0,
                matched_end=5.0,
                similarity=80.0,
            )

    def test_create_matched_end_le_start_raises(self):
        with pytest.raises(ValueError, match="invalid matched segment range"):
            DuplicateSegment.create(
                source_start=0.0,
                source_end=5.0,
                matched_video_id="vid-1",
                matched_video_name="v.mp4",
                matched_start=2.0,
                matched_end=1.0,
                similarity=80.0,
            )

    def test_create_similarity_out_of_range_raises(self):
        with pytest.raises(ValueError, match="similarity"):
            DuplicateSegment.create(
                source_start=0.0,
                source_end=5.0,
                matched_video_id="vid-1",
                matched_video_name="v.mp4",
                matched_start=0.0,
                matched_end=5.0,
                similarity=101.0,
            )

    def test_create_negative_similarity_raises(self):
        with pytest.raises(ValueError, match="similarity"):
            DuplicateSegment.create(
                source_start=0.0,
                source_end=5.0,
                matched_video_id="vid-1",
                matched_video_name="v.mp4",
                matched_start=0.0,
                matched_end=5.0,
                similarity=-1.0,
            )
