"""
Tests for Scene Slot Bundle Service.
[AC-SCENE-SLOT-01] 场景-槽位映射配置服务测试
"""

import pytest
from unittest.mock import AsyncMock, MagicMock, patch
from uuid import uuid4

from app.models.entities import (
    SceneSlotBundle,
    SceneSlotBundleCreate,
    SceneSlotBundleUpdate,
    SceneSlotBundleStatus,
    SlotDefinition,
)
from app.services.scene_slot_bundle_service import SceneSlotBundleService


@pytest.fixture
def mock_session():
    """Mock database session."""
    session = AsyncMock()
    session.execute = AsyncMock()
    session.add = MagicMock()
    session.flush = AsyncMock()
    session.delete = AsyncMock()
    return session


@pytest.fixture
def sample_slot_definition():
    """Sample slot definition for testing."""
    return SlotDefinition(
        id=uuid4(),
        tenant_id="test_tenant",
        slot_key="course_type",
        type="string",
        required=True,
        ask_back_prompt="请问您想咨询哪种类型的课程？",
    )


@pytest.fixture
def sample_bundle():
    """Sample scene slot bundle for testing."""
    return SceneSlotBundle(
        id=uuid4(),
        tenant_id="test_tenant",
        scene_key="open_consult",
        scene_name="开放咨询",
        description="开放咨询场景的槽位配置",
        required_slots=["course_type", "grade"],
        optional_slots=["region"],
        slot_priority=["course_type", "grade", "region"],
        completion_threshold=1.0,
        ask_back_order="priority",
        status=SceneSlotBundleStatus.ACTIVE.value,
        version=1,
    )


class TestSceneSlotBundleService:
    """Test cases for SceneSlotBundleService."""
    
    @pytest.mark.asyncio
    async def test_list_bundles(self, mock_session, sample_bundle):
        """Test listing scene slot bundles."""
        mock_result = MagicMock()
        mock_result.scalars.return_value.all.return_value = [sample_bundle]
        mock_session.execute.return_value = mock_result
        
        service = SceneSlotBundleService(mock_session)
        bundles = await service.list_bundles("test_tenant")
        
        assert len(bundles) == 1
        assert bundles[0].scene_key == "open_consult"
    
    @pytest.mark.asyncio
    async def test_list_bundles_with_status_filter(self, mock_session, sample_bundle):
        """Test listing scene slot bundles with status filter."""
        mock_result = MagicMock()
        mock_result.scalars.return_value.all.return_value = [sample_bundle]
        mock_session.execute.return_value = mock_result
        
        service = SceneSlotBundleService(mock_session)
        bundles = await service.list_bundles("test_tenant", status="active")
        
        assert len(bundles) == 1
    
    @pytest.mark.asyncio
    async def test_get_bundle_by_id(self, mock_session, sample_bundle):
        """Test getting a bundle by ID."""
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = sample_bundle
        mock_session.execute.return_value = mock_result
        
        service = SceneSlotBundleService(mock_session)
        bundle = await service.get_bundle("test_tenant", str(sample_bundle.id))
        
        assert bundle is not None
        assert bundle.scene_key == "open_consult"
    
    @pytest.mark.asyncio
    async def test_get_bundle_by_scene_key(self, mock_session, sample_bundle):
        """Test getting a bundle by scene key."""
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = sample_bundle
        mock_session.execute.return_value = mock_result
        
        service = SceneSlotBundleService(mock_session)
        bundle = await service.get_bundle_by_scene_key("test_tenant", "open_consult")
        
        assert bundle is not None
        assert bundle.scene_key == "open_consult"
    
    @pytest.mark.asyncio
    async def test_get_active_bundle_by_scene(self, mock_session, sample_bundle):
        """Test getting an active bundle by scene key."""
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = sample_bundle
        mock_session.execute.return_value = mock_result
        
        service = SceneSlotBundleService(mock_session)
        bundle = await service.get_active_bundle_by_scene("test_tenant", "open_consult")
        
        assert bundle is not None
        assert bundle.status == SceneSlotBundleStatus.ACTIVE.value
    
    @pytest.mark.asyncio
    async def test_create_bundle_success(self, mock_session, sample_slot_definition):
        """Test creating a scene slot bundle successfully."""
        mock_result = MagicMock()
        mock_result.scalars.return_value.all.return_value = [sample_slot_definition]
        mock_session.execute.return_value = mock_result
        
        service = SceneSlotBundleService(mock_session)
        
        with patch.object(service, '_validate_slot_keys', new_callable=AsyncMock) as mock_validate:
            mock_validate.return_value = {"course_type"}
            
            with patch.object(service, 'get_bundle_by_scene_key', new_callable=AsyncMock) as mock_get:
                mock_get.return_value = None
                
                bundle_create = SceneSlotBundleCreate(
                    scene_key="new_scene",
                    scene_name="新场景",
                    required_slots=["course_type"],
                    optional_slots=[],
                )
                
                bundle = await service.create_bundle("test_tenant", bundle_create)
                
                assert bundle is not None
                assert bundle.scene_key == "new_scene"
                mock_session.add.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_create_bundle_duplicate_scene_key(self, mock_session, sample_bundle):
        """Test creating a bundle with duplicate scene key."""
        service = SceneSlotBundleService(mock_session)
        
        with patch.object(service, 'get_bundle_by_scene_key', new_callable=AsyncMock) as mock_get:
            mock_get.return_value = sample_bundle
            
            with patch.object(service, '_validate_bundle_data', new_callable=AsyncMock) as mock_validate:
                mock_validate.return_value = []
                
                bundle_create = SceneSlotBundleCreate(
                    scene_key="open_consult",
                    scene_name="开放咨询",
                )
                
                with pytest.raises(ValueError, match="已存在"):
                    await service.create_bundle("test_tenant", bundle_create)
    
    @pytest.mark.asyncio
    async def test_create_bundle_validation_error(self, mock_session):
        """Test creating a bundle with validation error."""
        service = SceneSlotBundleService(mock_session)
        
        with patch.object(service, '_validate_bundle_data', new_callable=AsyncMock) as mock_validate:
            mock_validate.return_value = ["必填和可选槽位存在交叉"]
            
            bundle_create = SceneSlotBundleCreate(
                scene_key="new_scene",
                scene_name="新场景",
                required_slots=["course_type"],
                optional_slots=["course_type"],
            )
            
            with pytest.raises(ValueError, match="交叉"):
                await service.create_bundle("test_tenant", bundle_create)
    
    @pytest.mark.asyncio
    async def test_update_bundle_success(self, mock_session, sample_bundle):
        """Test updating a scene slot bundle successfully."""
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = sample_bundle
        mock_session.execute.return_value = mock_result
        
        service = SceneSlotBundleService(mock_session)
        
        with patch.object(service, '_validate_bundle_data', new_callable=AsyncMock) as mock_validate:
            mock_validate.return_value = []
            
            bundle_update = SceneSlotBundleUpdate(
                scene_name="更新后的场景名称",
            )
            
            bundle = await service.update_bundle("test_tenant", str(sample_bundle.id), bundle_update)
            
            assert bundle is not None
            assert bundle.scene_name == "更新后的场景名称"
            assert bundle.version == 2
    
    @pytest.mark.asyncio
    async def test_delete_bundle_success(self, mock_session, sample_bundle):
        """Test deleting a scene slot bundle successfully."""
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = sample_bundle
        mock_session.execute.return_value = mock_result
        
        service = SceneSlotBundleService(mock_session)
        
        success = await service.delete_bundle("test_tenant", str(sample_bundle.id))
        
        assert success is True
        mock_session.delete.assert_called_once()
    
    @pytest.mark.asyncio
    async def test_delete_bundle_not_found(self, mock_session):
        """Test deleting a non-existent bundle."""
        mock_result = MagicMock()
        mock_result.scalar_one_or_none.return_value = None
        mock_session.execute.return_value = mock_result
        
        service = SceneSlotBundleService(mock_session)
        
        success = await service.delete_bundle("test_tenant", str(uuid4()))
        
        assert success is False


class TestSceneSlotBundleValidation:
    """Test cases for bundle validation."""
    
    @pytest.mark.asyncio
    async def test_validate_required_optional_overlap(self, mock_session, sample_slot_definition):
        """Test validation for required and optional slots overlap."""
        mock_result = MagicMock()
        mock_result.scalars.return_value.all.return_value = [sample_slot_definition]
        mock_session.execute.return_value = mock_result
        
        service = SceneSlotBundleService(mock_session)
        
        errors = await service._validate_bundle_data(
            tenant_id="test_tenant",
            required_slots=["course_type"],
            optional_slots=["course_type"],
            slot_priority=None,
        )
        
        assert len(errors) > 0
        assert any("交叉" in e for e in errors)
    
    @pytest.mark.asyncio
    async def test_validate_priority_unknown_slots(self, mock_session, sample_slot_definition):
        """Test validation for unknown slots in priority list."""
        mock_result = MagicMock()
        mock_result.scalars.return_value.all.return_value = [sample_slot_definition]
        mock_session.execute.return_value = mock_result
        
        service = SceneSlotBundleService(mock_session)
        
        errors = await service._validate_bundle_data(
            tenant_id="test_tenant",
            required_slots=["course_type"],
            optional_slots=[],
            slot_priority=["course_type", "unknown_slot"],
        )
        
        assert len(errors) > 0
        assert any("未定义" in e for e in errors)
