#!/usr/bin/env python3
"""DDDバリデーターのテストスイート

TDD RED Phase - 失敗するテストを先に作成


仕様書: SPEC-UNIT-TEST
"""

import unittest
from pathlib import Path

import pytest

from noveler.tools.ddd_validator import (
    AnemicModelWarning,
    DDDValidationResult,
    DDDValidator,
    LayerViolation,
    RepositoryPatternViolation,
    format_cli_output,
    get_exit_code,
)


class TestDDDValidator(unittest.TestCase):
    """DDDバリデーターのテストケース"""

    def setUp(self) -> None:
        """テストセットアップ"""
        self.validator = DDDValidator()
        self.test_project_root = Path(__file__).parent / "test_ddd_project"

    @pytest.mark.spec("SPEC-DDD_VALIDATOR-DETECT_LAYER_VIOLATI")
    def test_detect_layer_violation_domain_imports_infrastructure(self) -> None:
        """ドメイン層がインフラ層をインポートしている違反を検出"""
        # Arrange
        test_file = self.test_project_root / "domain" / "entities.py"
        test_content = """
from noveler.infrastructure.persistence.file_repository import FileRepository

class Episode:
    def __init__(self) -> None:
        self.repo = FileRepository()  # 違反!
"""

        # Act
        violations = self.validator.check_layer_dependencies(test_file, test_content)

        # Assert
        assert len(violations) == 1
        assert isinstance(violations[0], LayerViolation)
        assert "infrastructure" in violations[0].message

    @pytest.mark.spec("SPEC-DDD_VALIDATOR-NO_LAYER_VIOLATION_W")
    def test_no_layer_violation_when_proper_dependencies(self) -> None:
        """適切な依存関係の場合は違反なし"""
        # Arrange
        test_file = self.test_project_root / "application" / "use_cases.py"
        test_content = """
from noveler.domain.entities import Episode
from noveler.domain.repositories import EpisodeRepository

class CreateEpisodeUseCase:
    def __init__(self, repo: EpisodeRepository) -> None:
        self.repo = repo
"""

        # Act
        violations = self.validator.check_layer_dependencies(test_file, test_content)

        # Assert
        assert len(violations) == 0

    @pytest.mark.spec("SPEC-DDD_VALIDATOR-DETECT_ANEMIC_DOMAIN")
    def test_detect_anemic_domain_model(self) -> None:
        """貧血ドメインモデルを検出"""
        # Arrange
        test_content = """
class Episode:
    def __init__(self, title: object, content: object) -> None:
        self.title = title
        self.content = content
        self.status = "draft"

    # ゲッター・セッターのみで、ビジネスロジックなし
"""

        # Act
        warnings = self.validator.check_rich_domain_models(test_content, "Episode")

        # Assert
        assert len(warnings) == 1
        assert isinstance(warnings[0], AnemicModelWarning)
        assert "Episode" in warnings[0].entity_name

    @pytest.mark.spec("SPEC-DDD_VALIDATOR-RICH_DOMAIN_MODEL_NO")
    def test_rich_domain_model_no_warning(self) -> None:
        """リッチドメインモデルは警告なし"""
        # Arrange
        test_content = """
class Episode:
    def __init__(self, title: object, content: object) -> None:
        self.title = title
        self.content = content
        self.status = "draft"

    def publish(self):
        if self.status != "draft":
            raise ValueError("Only draft episodes can be published")
        if len(self.content) < 1000:
            raise ValueError("Episode too short")
        self.status = "published"

    def complete_draft(self):
        self.status = "completed"
"""

        # Act
        warnings = self.validator.check_rich_domain_models(test_content, "Episode")

        # Assert
        assert len(warnings) == 0

    @pytest.mark.spec("SPEC-DDD_VALIDATOR-DETECT_REPOSITORY_IN")
    def test_detect_repository_interface_in_wrong_layer(self) -> None:
        """リポジトリインターフェースが間違った層にある違反を検出"""
        # Arrange
        infra_file = self.test_project_root / "infrastructure" / "repositories.py"
        interface_content = """
from abc import ABC, abstractmethod

class EpisodeRepository(ABC):  # インターフェースがインフラ層にある!
    @abstractmethod
    def save(self, episode: object):
        pass
"""

        # Act
        violations = self.validator.check_repository_pattern(
            infra_file,
            interface_content,
        )

        # Assert
        assert len(violations) == 1
        assert isinstance(violations[0], RepositoryPatternViolation)

    @pytest.mark.spec("SPEC-DDD_VALIDATOR-REPOSITORY_IMPLEMENT")
    def test_repository_implementation_in_infrastructure_ok(self) -> None:
        """リポジトリ実装がインフラ層にあるのは正しい"""
        # Arrange
        infra_file = self.test_project_root / "infrastructure" / "persistence" / "yaml_repository.py"
        impl_content = """

class YamlEpisodeRepository(EpisodeRepository):
    def save(self, episode: Episode):
        # 実装
        pass
"""

        # Act
        violations = self.validator.check_repository_pattern(
            infra_file,
            impl_content,
        )

        # Assert
        assert len(violations) == 0

    @pytest.mark.spec("SPEC-DDD_VALIDATOR-VALIDATE_ENTIRE_PROJ")
    def test_validate_entire_project(self) -> None:
        """プロジェクト全体のDDD準拠度を検証"""
        # Arrange
        project_root = Path(__file__).parent.parent

        # Act
        result = self.validator.validate_project(project_root)

        # Assert
        assert isinstance(result, DDDValidationResult)
        assert "layer_violations" in result.summary
        assert "anemic_models" in result.summary
        assert "repository_violations" in result.summary
        assert result.compliance_score >= 0
        assert result.compliance_score <= 100

    @pytest.mark.spec("SPEC-DDD_VALIDATOR-GENERATE_VALIDATION_")
    def test_generate_validation_report(self) -> None:
        """検証レポートの生成"""
        # Arrange
        result = DDDValidationResult()
        result.layer_violations = [
            LayerViolation("domain/entities.py", 10, "Domain imports infrastructure"),
        ]
        result.anemic_warnings = [
            AnemicModelWarning("Episode", 0, 2),
        ]

        # Act
        report = self.validator.generate_report(result)

        # Assert
        assert "DDD Validation Report" in report
        assert "Layer Violations: 1" in report
        assert "Anemic Models: 1" in report
        assert "Compliance Score:" in report

    @pytest.mark.spec("SPEC-DDD_VALIDATOR-CUSTOM_VALIDATION_RU")
    def test_custom_validation_rules(self) -> None:
        """カスタム検証ルールの追加"""

        # Arrange
        def custom_rule(file_path: object, content: object):
            violations = []
            if "# TODO" in content:
                violations.append("TODO found in production code")
            return violations

        self.validator.add_custom_rule("no_todos", custom_rule)

        # Act
        content_with_todo = "# TODO: Implement this later\nclass Episode: pass"
        violations = self.validator.apply_custom_rules(
            Path("domain/entities.py"),
            content_with_todo,
        )

        # Assert
        assert len(violations) == 1
        assert "TODO" in violations[0]

    @pytest.mark.spec("SPEC-DDD_VALIDATOR-VALIDATE_SERVICE_LAY")
    def test_validate_service_layer_logic(self) -> None:
        """ドメインサービスにビジネスロジックがあることを確認"""
        # Arrange
        service_content = """
class PricingService:
    def calculate_discount(self, price: object, customer_type: object):
        if customer_type == "premium":
            return price * 0.8
        return price
"""

        # Act
        has_logic = self.validator.check_domain_service_logic(
            service_content,
            "PricingService",
        )

        # Assert
        assert has_logic


class TestDDDValidationResult(unittest.TestCase):
    """DDD検証結果のテスト"""

    @pytest.mark.spec("SPEC-DDD_VALIDATOR-CALCULATE_COMPLIANCE")
    def test_calculate_compliance_score(self) -> None:
        """コンプライアンススコアの計算"""
        # Arrange
        result = DDDValidationResult()
        result.layer_violations = [LayerViolation("", 0, "")] * 2
        result.anemic_warnings = [AnemicModelWarning("", 0, 0)] * 3
        result.repository_violations = []
        result.total_entities = 10
        result.total_files = 50

        # Act
        score = result.calculate_compliance_score()

        # Assert
        assert score > 0
        assert score < 100
        # スコアは違反が多いほど低い(しかし0より大きい)
        assert score > 0
        assert score <= 100

    @pytest.mark.spec("SPEC-DDD_VALIDATOR-COMPLIANCE_SCORE_PER")
    def test_compliance_score_perfect(self) -> None:
        """違反なしの場合は100点"""
        # Arrange
        result = DDDValidationResult()
        result.layer_violations = []
        result.anemic_warnings = []
        result.repository_violations = []
        result.total_entities = 5
        result.total_files = 20

        # Act
        score = result.calculate_compliance_score()

        # Assert
        assert score == 100


class TestValidatorCLI(unittest.TestCase):
    """バリデーターCLIインターフェースのテスト"""

    @pytest.mark.spec("SPEC-DDD_VALIDATOR-CLI_OUTPUT_FORMAT")
    def test_cli_output_format(self) -> None:
        """CLI出力フォーマットのテスト"""
        # Arrange

        result = DDDValidationResult()
        result.layer_violations = [LayerViolation("test.py", 10, "Test violation")]

        # Act
        output = format_cli_output(result)

        # Assert
        assert "🔍 DDD Validation Results" in output
        assert "❌ Layer Violations:" in output
        assert "📊 Compliance Score:" in output

    @pytest.mark.spec("SPEC-DDD_VALIDATOR-EXIT_CODE_ON_VIOLATI")
    def test_exit_code_on_violations(self) -> None:
        """違反がある場合の終了コード"""
        # Arrange

        result = DDDValidationResult()
        result.layer_violations = [LayerViolation("", 0, "")]

        # Act
        exit_code = get_exit_code(result, threshold=80)

        # Assert
        assert exit_code == 1  # 失敗

    @pytest.mark.spec("SPEC-DDD_VALIDATOR-EXIT_CODE_ON_SUCCESS")
    def test_exit_code_on_success(self) -> None:
        """違反がない場合の終了コード"""
        # Arrange

        result = DDDValidationResult()
        result.compliance_score = 95

        # Act
        exit_code = get_exit_code(result, threshold=80)

        # Assert
        assert exit_code == 0  # 成功


if __name__ == "__main__":
    unittest.main()
