"""分类领域模型单元测试 - 纯逻辑部分。"""

from __future__ import annotations

import pytest
from domain.classification import (
    AssetClassification,
    AssetLibraryKind,
    ClassificationJob,
    ClassificationJobStatus,
    ClassificationStatus,
    IngestJobStatus,
)


class TestAssetLibraryKind:
    """素材库类型枚举。"""

    def test_video_value(self):
        assert AssetLibraryKind.VIDEO == "video"

    def test_voice_value(self):
        assert AssetLibraryKind.VOICE == "voice"

    def test_image_value(self):
        assert AssetLibraryKind.IMAGE == "image"

    def test_is_str_enum(self):
        assert isinstance(AssetLibraryKind.VIDEO, str)
        assert AssetLibraryKind.VIDEO + "_test" == "video_test"

    def test_members_count(self):
        assert len(AssetLibraryKind) == 3


class TestIngestJobStatus:
    """导入任务状态枚举。"""

    def test_pending_value(self):
        assert IngestJobStatus.PENDING == "pending"

    def test_processing_value(self):
        assert IngestJobStatus.PROCESSING == "processing"

    def test_completed_value(self):
        assert IngestJobStatus.COMPLETED == "completed"

    def test_failed_value(self):
        assert IngestJobStatus.FAILED == "failed"

    def test_members_count(self):
        assert len(IngestJobStatus) == 4


class TestClassificationJobStatusMissing:
    """ClassificationJobStatus._missing_ 兼容性测试。"""

    def test_standard_values(self):
        """标准值正常解析。"""
        assert ClassificationJobStatus("pending") == ClassificationJobStatus.PENDING
        assert ClassificationJobStatus("processing") == ClassificationJobStatus.PROCESSING
        assert ClassificationJobStatus("completed") == ClassificationJobStatus.COMPLETED
        assert ClassificationJobStatus("failed") == ClassificationJobStatus.FAILED

    def test_done_maps_to_completed(self):
        """历史值 done 映射到 COMPLETED。"""
        assert ClassificationJobStatus("done") == ClassificationJobStatus.COMPLETED

    def test_success_maps_to_completed(self):
        assert ClassificationJobStatus("success") == ClassificationJobStatus.COMPLETED

    def test_finished_maps_to_completed(self):
        assert ClassificationJobStatus("finished") == ClassificationJobStatus.COMPLETED

    def test_complete_maps_to_completed(self):
        assert ClassificationJobStatus("complete") == ClassificationJobStatus.COMPLETED

    def test_fail_maps_to_failed(self):
        assert ClassificationJobStatus("fail") == ClassificationJobStatus.FAILED

    def test_error_maps_to_failed(self):
        assert ClassificationJobStatus("error") == ClassificationJobStatus.FAILED

    def test_err_maps_to_failed(self):
        assert ClassificationJobStatus("err") == ClassificationJobStatus.FAILED

    def test_process_maps_to_processing(self):
        assert ClassificationJobStatus("process") == ClassificationJobStatus.PROCESSING

    def test_running_maps_to_processing(self):
        assert ClassificationJobStatus("running") == ClassificationJobStatus.PROCESSING

    def test_run_maps_to_processing(self):
        assert ClassificationJobStatus("run") == ClassificationJobStatus.PROCESSING

    def test_unknown_value_defaults_to_pending(self):
        """未知值兜底为 PENDING。"""
        assert ClassificationJobStatus("unknown") == ClassificationJobStatus.PENDING
        assert ClassificationJobStatus("whatever") == ClassificationJobStatus.PENDING
        assert ClassificationJobStatus("") == ClassificationJobStatus.PENDING

    def test_case_insensitive(self):
        """大小写不敏感。"""
        assert ClassificationJobStatus("DONE") == ClassificationJobStatus.COMPLETED
        assert ClassificationJobStatus("Done") == ClassificationJobStatus.COMPLETED
        assert ClassificationJobStatus("FAIL") == ClassificationJobStatus.FAILED
        assert ClassificationJobStatus("Error") == ClassificationJobStatus.FAILED

    def test_stripped(self):
        """前后空白字符被忽略。"""
        assert ClassificationJobStatus("  done  ") == ClassificationJobStatus.COMPLETED
        assert ClassificationJobStatus("\tfail\n") == ClassificationJobStatus.FAILED

    def test_none_returns_pending(self):
        """None 值也返回 PENDING（不报错）。"""
        assert ClassificationJobStatus(None) == ClassificationJobStatus.PENDING

    def test_integer_returns_pending(self):
        """非字符串值返回 PENDING。"""
        assert ClassificationJobStatus(123) == ClassificationJobStatus.PENDING


class TestClassificationStatusAlias:
    """向后兼容别名。"""

    def test_alias_same_class(self):
        assert ClassificationStatus is ClassificationJobStatus

    def test_alias_values_same(self):
        assert ClassificationStatus.PENDING == ClassificationJobStatus.PENDING
        assert ClassificationStatus.COMPLETED == ClassificationJobStatus.COMPLETED


class TestAssetClassification:
    """素材分类枚举。"""

    def test_scenic(self):
        assert AssetClassification.SCENIC == "scenic"

    def test_product(self):
        assert AssetClassification.PRODUCT == "product"

    def test_person(self):
        assert AssetClassification.PERSON == "person"

    def test_animal(self):
        assert AssetClassification.ANIMAL == "animal"

    def test_food(self):
        assert AssetClassification.FOOD == "food"

    def test_tech(self):
        assert AssetClassification.TECH == "tech"

    def test_sport(self):
        assert AssetClassification.SPORT == "sport"

    def test_music(self):
        assert AssetClassification.MUSIC == "music"

    def test_other(self):
        assert AssetClassification.OTHER == "other"

    def test_members_count(self):
        assert len(AssetClassification) == 9


class TestClassificationJobCreate:
    """ClassificationJob.create 工厂方法。"""

    def test_create_basic(self):
        job = ClassificationJob.create(project_id="proj-1", asset_id="asset-1")
        assert job.project_id == "proj-1"
        assert job.asset_id == "asset-1"
        assert job.status == ClassificationJobStatus.PENDING
        assert job.classification == ""
        assert job.confidence == 0.0
        assert job.error_message == ""
        assert job.id  # 自动生成的 ID 非空

    def test_create_strips_whitespace(self):
        job = ClassificationJob.create(project_id="  proj-1  ", asset_id="\tasset-1\n")
        assert job.project_id == "proj-1"
        assert job.asset_id == "asset-1"

    def test_create_empty_project_id_raises(self):
        with pytest.raises(ValueError, match="project_id 不能为空"):
            ClassificationJob.create(project_id="", asset_id="asset-1")

    def test_create_whitespace_project_id_raises(self):
        with pytest.raises(ValueError, match="project_id 不能为空"):
            ClassificationJob.create(project_id="   ", asset_id="asset-1")

    def test_create_empty_asset_id_raises(self):
        with pytest.raises(ValueError, match="asset_id 不能为空"):
            ClassificationJob.create(project_id="proj-1", asset_id="")

    def test_create_whitespace_asset_id_raises(self):
        with pytest.raises(ValueError, match="asset_id 不能为空"):
            ClassificationJob.create(project_id="proj-1", asset_id="  \t  ")

    def test_create_generates_unique_ids(self):
        job1 = ClassificationJob.create(project_id="p", asset_id="a")
        job2 = ClassificationJob.create(project_id="p", asset_id="a")
        assert job1.id != job2.id

    def test_create_id_is_hex(self):
        job = ClassificationJob.create(project_id="p", asset_id="a")
        assert job.created_at.tzinfo is not None
        assert job.updated_at.tzinfo is not None


class TestClassificationJobState:
    """ClassificationJob 状态操作测试"""

    def test_set_processing(self):
        job = ClassificationJob.create(project_id="proj-1", asset_id="asset-1")
        job.status = ClassificationJobStatus.PROCESSING
        assert job.status == ClassificationJobStatus.PROCESSING

    def test_set_completed_with_result(self):
        job = ClassificationJob.create(project_id="proj-1", asset_id="asset-1")
        job.status = ClassificationJobStatus.COMPLETED
        job.classification = AssetClassification.SCENIC
        job.confidence = 0.95
        assert job.status == ClassificationJobStatus.COMPLETED
        assert job.classification == "scenic"
        assert job.confidence == pytest.approx(0.95)

    def test_set_failed_with_error(self):
        job = ClassificationJob.create(project_id="proj-1", asset_id="asset-1")
        job.status = ClassificationJobStatus.FAILED
        job.error_message = "model timeout"
        assert job.status == ClassificationJobStatus.FAILED
        assert job.error_message == "model timeout"

    def test_confidence_range_zero(self):
        job = ClassificationJob.create(project_id="proj-1", asset_id="asset-1")
        job.confidence = 0.0
        assert job.confidence == 0.0

    def test_confidence_range_one(self):
        job = ClassificationJob.create(project_id="proj-1", asset_id="asset-1")
        job.confidence = 1.0
        assert job.confidence == 1.0


class TestClassificationJobStatusMissingAliases:
    """ClassificationJobStatus._missing_ 兼容行为测试"""

    def test_done_maps_to_completed(self):
        assert ClassificationJobStatus("done") == ClassificationJobStatus.COMPLETED

    def test_success_maps_to_completed(self):
        assert ClassificationJobStatus("success") == ClassificationJobStatus.COMPLETED

    def test_finished_maps_to_completed(self):
        assert ClassificationJobStatus("finished") == ClassificationJobStatus.COMPLETED

    def test_complete_maps_to_completed(self):
        assert ClassificationJobStatus("complete") == ClassificationJobStatus.COMPLETED

    def test_fail_maps_to_failed(self):
        assert ClassificationJobStatus("fail") == ClassificationJobStatus.FAILED

    def test_error_maps_to_failed(self):
        assert ClassificationJobStatus("error") == ClassificationJobStatus.FAILED

    def test_err_maps_to_failed(self):
        assert ClassificationJobStatus("err") == ClassificationJobStatus.FAILED

    def test_unknown_maps_to_pending(self):
        assert ClassificationJobStatus("unknown_status") == ClassificationJobStatus.PENDING

    def test_case_insensitive_mapping(self):
        assert ClassificationJobStatus("DONE") == ClassificationJobStatus.COMPLETED
        assert ClassificationJobStatus("Done") == ClassificationJobStatus.COMPLETED

    def test_whitespace_stripped(self):
        assert ClassificationJobStatus("  done  ") == ClassificationJobStatus.COMPLETED


class TestClassificationJobExtended:
    """ClassificationJob 深度补充测试"""

    def test_id_is_hex(self):
        job = ClassificationJob.create(project_id="p", asset_id="a")
        int(job.id, 16)

    def test_empty_classification(self):
        job = ClassificationJob.create(project_id="p", asset_id="a")
        assert job.classification == ""

    def test_zero_confidence(self):
        job = ClassificationJob.create(project_id="p", asset_id="a")
        assert job.confidence == 0.0

    def test_high_confidence(self):
        job = ClassificationJob.create(project_id="p", asset_id="a")
        job.confidence = 0.99
        assert job.confidence == pytest.approx(0.99)

    def test_negative_confidence(self):
        job = ClassificationJob.create(project_id="p", asset_id="a")
        job.confidence = -0.1
        assert job.confidence == pytest.approx(-0.1)

    def test_confidence_over_one(self):
        job = ClassificationJob.create(project_id="p", asset_id="a")
        job.confidence = 1.5
        assert job.confidence == pytest.approx(1.5)

    def test_empty_error_message(self):
        job = ClassificationJob.create(project_id="p", asset_id="a")
        assert job.error_message == ""

    def test_long_error_message(self):
        job = ClassificationJob.create(project_id="p", asset_id="a")
        long_msg = "error" * 100
        job.error_message = long_msg
        assert job.error_message == long_msg
        assert len(job.error_message) == 500

    def test_status_with_string_assignment(self):
        job = ClassificationJob.create(project_id="p", asset_id="a")
        job.status = "processing"
        assert job.status == ClassificationJobStatus.PROCESSING


class TestAssetLibraryKindExtended:
    """AssetLibraryKind 深度补充测试"""

    def test_image_value(self):
        assert AssetLibraryKind.IMAGE == "image"

    def test_all_three_kinds(self):
        assert len(AssetLibraryKind) == 3

    def test_is_string_enum(self):
        assert isinstance(AssetLibraryKind.VIDEO, str)

    def test_from_string(self):
        assert AssetLibraryKind("video") == AssetLibraryKind.VIDEO


class TestIngestJobStatusExtended:
    """IngestJobStatus 深度补充测试"""

    def test_is_string_enum(self):
        assert isinstance(IngestJobStatus.PENDING, str)

    def test_total_count(self):
        assert len(IngestJobStatus) == 4

    def test_from_string(self):
        assert IngestJobStatus("pending") == IngestJobStatus.PENDING


class TestAssetClassificationExtended:
    """AssetClassification 深度补充测试"""

    def test_total_count(self):
        assert len(AssetClassification) == 9

    def test_is_string_enum(self):
        assert isinstance(AssetClassification.SCENIC, str)

    def test_from_string(self):
        assert AssetClassification("scenic") == AssetClassification.SCENIC

    def test_other_category(self):
        assert AssetClassification.OTHER == "other"
