"""
Unit tests for data model mappers

Tests mapper conversion correctness between entities and schemas.
"""
import pytest
from datetime import datetime
import uuid

from src.models.entities import (
    ProjectDB,
    AssetDB,
    EpisodeDB,
    StoryboardDB,
    TaskDB,
    CanvasMetadataDB,
)
from src.models.schemas import (
    CreateProjectRequest,
    UpdateProjectRequest,
    CreateCharacterAssetRequest,
    CreateSceneAssetRequest,
    CreatePropAssetRequest,
    UpdateAssetRequest,
    CreateEpisodeRequest,
    UpdateEpisodeRequest,
    CreateStoryboardRequest,
    UpdateStoryboardRequest,
)
from src.mappers import (
    ProjectMapper,
    AssetMapper,
    EpisodeMapper,
    StoryboardMapper,
    TaskMapper,
    CanvasMetadataMapper,
)


class TestProjectMapper:
    """Test ProjectMapper conversion correctness"""
    
    def test_to_entity_from_create_request(self):
        """Test converting CreateProjectRequest to ProjectDB entity"""
        request = CreateProjectRequest(
            name="Test Project",
            description="Test Description",
            type="video",
            chapters=[{"title": "Chapter 1"}],
            assets=[{"name": "Asset 1"}]
        )
        
        entity = ProjectMapper.to_entity(request)
        
        assert entity.name == "Test Project"
        assert entity.description == "Test Description"
        assert entity.type == "video"
        assert entity.status == "active"
        assert entity.id is not None
        assert entity.created_at is not None
        assert entity.updated_at is not None
        assert entity.chapters == [{"title": "Chapter 1"}]
    
    def test_to_entity_with_custom_id(self):
        """Test converting with custom ID"""
        request = CreateProjectRequest(name="Test Project")
        custom_id = str(uuid.uuid4())
        
        entity = ProjectMapper.to_entity(request, project_id=custom_id)
        
        assert entity.id == custom_id
    
    def test_to_schema_from_entity(self):
        """Test converting ProjectDB entity to schema"""
        now = datetime.now().timestamp()
        entity = ProjectDB(
            id="proj_123",
            name="Test Project",
            description="Test Description",
            type="video",
            status="active",
            created_at=now,
            updated_at=now,
            resolution="1920x1080",
            ratio="16:9"
        )
        
        schema = ProjectMapper.to_schema(entity)
        
        assert schema.id == "proj_123"
        assert schema.name == "Test Project"
        assert schema.description == "Test Description"
        assert schema.type == "video"
        assert schema.status == "active"
        assert schema.resolution == "1920x1080"
        assert schema.ratio == "16:9"
    
    def test_update_entity(self):
        """Test updating ProjectDB entity from UpdateProjectRequest"""
        entity = ProjectDB(
            name="Original Name",
            description="Original Description",
            resolution="1280x720"
        )
        
        update_request = UpdateProjectRequest(
            name="Updated Name",
            resolution="1920x1080"
        )
        
        updated = ProjectMapper.update_entity(entity, update_request)
        
        assert updated.name == "Updated Name"
        assert updated.resolution == "1920x1080"
        assert updated.description == "Original Description"  # unchanged
        assert updated.updated_at > entity.created_at
    
    def test_update_entity_partial(self):
        """Test partial update of ProjectDB entity"""
        entity = ProjectDB(
            name="Original Name",
            description="Original Description",
            resolution="1280x720"
        )
        
        update_request = UpdateProjectRequest(name="Updated Name")
        
        updated = ProjectMapper.update_entity(entity, update_request)
        
        assert updated.name == "Updated Name"
        assert updated.description == "Original Description"
        assert updated.resolution == "1280x720"
    
    def test_roundtrip_conversion(self):
        """Test roundtrip conversion: Request -> Entity -> Schema"""
        request = CreateProjectRequest(
            name="Test Project",
            description="Test Description",
            type="video"
        )
        
        entity = ProjectMapper.to_entity(request)
        schema = ProjectMapper.to_schema(entity)
        
        assert schema.name == request.name
        assert schema.description == request.description
        assert schema.type == request.type


class TestAssetMapper:
    """Test AssetMapper conversion correctness"""
    
    def test_to_entity_character_asset(self):
        """Test converting CreateCharacterAssetRequest to AssetDB"""
        request = CreateCharacterAssetRequest(
            type="character",
            name="Hero",
            desc="Main character",
            tags=["protagonist"],
            age="25",
            gender="male",
            role="hero",
            appearance="Tall and strong"
        )
        
        entity = AssetMapper.to_entity(request, project_id="proj_123")
        
        assert entity.project_id == "proj_123"
        assert entity.type == "character"
        assert entity.name == "Hero"
        assert entity.desc == "Main character"
        assert entity.tags == ["protagonist"]
        assert entity.extra_data["age"] == "25"
        assert entity.extra_data["gender"] == "male"
        assert entity.extra_data["role"] == "hero"
        assert entity.extra_data["appearance"] == "Tall and strong"
    
    def test_to_entity_scene_asset(self):
        """Test converting CreateSceneAssetRequest to AssetDB"""
        request = CreateSceneAssetRequest(
            type="scene",
            name="Forest",
            desc="Dark forest",
            location="Northern Woods",
            time_of_day="night",
            atmosphere="mysterious"
        )
        
        entity = AssetMapper.to_entity(request, project_id="proj_123")
        
        assert entity.type == "scene"
        assert entity.name == "Forest"
        assert entity.extra_data["location"] == "Northern Woods"
        assert entity.extra_data["time_of_day"] == "night"
        assert entity.extra_data["atmosphere"] == "mysterious"
    
    def test_to_entity_prop_asset(self):
        """Test converting CreatePropAssetRequest to AssetDB"""
        request = CreatePropAssetRequest(
            type="prop",
            name="Magic Sword",
            desc="Ancient sword",
            usage="weapon"
        )
        
        entity = AssetMapper.to_entity(request, project_id="proj_123")
        
        assert entity.type == "prop"
        assert entity.name == "Magic Sword"
        assert entity.extra_data["usage"] == "weapon"
    
    def test_to_schema_character_asset(self):
        """Test converting AssetDB to CharacterAsset schema"""
        entity = AssetDB(
            id="asset_123",
            project_id="proj_123",
            type="character",
            name="Hero",
            desc="Main character",
            tags=["protagonist"],
            extra_data={"age": "25", "gender": "male", "role": "hero"}
        )
        
        schema = AssetMapper.to_schema(entity)
        
        assert schema.id == "asset_123"
        assert schema.type == "character"
        assert schema.name == "Hero"
        assert schema.age == "25"
        assert schema.gender == "male"
        assert schema.role == "hero"
    
    def test_to_schema_scene_asset(self):
        """Test converting AssetDB to SceneAsset schema"""
        entity = AssetDB(
            id="asset_123",
            project_id="proj_123",
            type="scene",
            name="Forest",
            desc="Dark forest",
            extra_data={"location": "Northern Woods", "time_of_day": "night"}
        )
        
        schema = AssetMapper.to_schema(entity)
        
        assert schema.type == "scene"
        assert schema.name == "Forest"
        assert schema.location == "Northern Woods"
        assert schema.time_of_day == "night"
    
    def test_update_entity(self):
        """Test updating AssetDB entity"""
        entity = AssetDB(
            project_id="proj_123",
            type="character",
            name="Original Name",
            desc="Original Description",
            extra_data={"age": "25"}
        )
        
        update_request = UpdateAssetRequest(
            name="Updated Name",
            age="26"
        )
        
        updated = AssetMapper.update_entity(entity, update_request)
        
        assert updated.name == "Updated Name"
        assert updated.extra_data["age"] == "26"
        assert updated.desc == "Original Description"
    
    def test_roundtrip_conversion_character(self):
        """Test roundtrip conversion for character asset"""
        request = CreateCharacterAssetRequest(
            type="character",
            name="Hero",
            desc="Main character",
            age="25",
            gender="male"
        )
        
        entity = AssetMapper.to_entity(request, project_id="proj_123")
        schema = AssetMapper.to_schema(entity)
        
        assert schema.name == request.name
        assert schema.desc == request.desc
        assert schema.age == request.age
        assert schema.gender == request.gender


class TestEpisodeMapper:
    """Test EpisodeMapper conversion correctness"""
    
    def test_to_entity(self):
        """Test converting CreateEpisodeRequest to EpisodeDB"""
        request = CreateEpisodeRequest(
            title="Episode 1",
            order=1,
            desc="First episode",
            status="draft"
        )
        
        entity = EpisodeMapper.to_entity(request, project_id="proj_123")
        
        assert entity.project_id == "proj_123"
        assert entity.title == "Episode 1"
        assert entity.order_index == 1
        assert entity.desc == "First episode"
        assert entity.status == "draft"
        assert entity.id is not None
    
    def test_to_schema(self):
        """Test converting EpisodeDB to Episode schema"""
        entity = EpisodeDB(
            id="ep_123",
            project_id="proj_123",
            order_index=1,
            title="Episode 1",
            desc="First episode",
            content="Episode content",
            status="draft"
        )
        
        schema = EpisodeMapper.to_schema(entity)
        
        assert schema.id == "ep_123"
        assert schema.title == "Episode 1"
        assert schema.order == 1
        assert schema.desc == "First episode"
        assert schema.content == "Episode content"
        assert schema.status == "draft"
    
    def test_update_entity(self):
        """Test updating EpisodeDB entity"""
        entity = EpisodeDB(
            project_id="proj_123",
            order_index=1,
            title="Original Title",
            status="draft"
        )
        
        update_request = UpdateEpisodeRequest(
            title="Updated Title",
            status="production"
        )
        
        updated = EpisodeMapper.update_entity(entity, update_request)
        
        assert updated.title == "Updated Title"
        assert updated.status == "production"
        assert updated.order_index == 1  # unchanged
    
    def test_roundtrip_conversion(self):
        """Test roundtrip conversion for episode"""
        request = CreateEpisodeRequest(
            title="Episode 1",
            order=1,
            desc="First episode"
        )
        
        entity = EpisodeMapper.to_entity(request, project_id="proj_123")
        schema = EpisodeMapper.to_schema(entity)
        
        assert schema.title == request.title
        assert schema.order == request.order
        assert schema.desc == request.desc


class TestStoryboardMapper:
    """Test StoryboardMapper conversion correctness"""
    
    def test_to_entity(self):
        """Test converting CreateStoryboardRequest to StoryboardDB"""
        request = CreateStoryboardRequest(
            episode_id="ep_123",
            order=1,
            shot="Shot 1",
            desc="Opening scene",
            duration="5s",
            type="image",
            scene_id="scene_123",
            character_ids=["char_1", "char_2"],
            prop_ids=["prop_1"],
            camera_angle="wide",
            lens="50mm",
            location="forest",
            time="morning"
        )
        
        entity = StoryboardMapper.to_entity(request, project_id="proj_123")
        
        assert entity.project_id == "proj_123"
        assert entity.episode_id == "ep_123"
        assert entity.order_index == 1
        assert entity.shot == "Shot 1"
        assert entity.desc == "Opening scene"
        assert entity.duration == "5s"
        assert entity.type == "image"
        assert entity.scene_id == "scene_123"
        assert entity.character_ids == ["char_1", "char_2"]
        assert entity.prop_ids == ["prop_1"]
        assert entity.camera_angle == "wide"
        assert entity.lens == "50mm"
        assert entity.location == "forest"
        assert entity.time == "morning"
    
    def test_to_schema(self):
        """Test converting StoryboardDB to Storyboard schema"""
        entity = StoryboardDB(
            id="sb_123",
            project_id="proj_123",
            episode_id="ep_123",
            order_index=1,
            shot="Shot 1",
            desc="Opening scene",
            duration="5s",
            type="image",
            scene_id="scene_123",
            character_ids=["char_1"],
            camera_angle="wide",
            location="forest"
        )
        
        schema = StoryboardMapper.to_schema(entity)
        
        assert schema.id == "sb_123"
        assert schema.episode_id == "ep_123"
        assert schema.order == 1
        assert schema.shot == "Shot 1"
        assert schema.scene_id == "scene_123"
        assert schema.character_ids == ["char_1"]
        assert schema.camera_angle == "wide"
        assert schema.location == "forest"
    
    def test_update_entity(self):
        """Test updating StoryboardDB entity"""
        entity = StoryboardDB(
            project_id="proj_123",
            episode_id="ep_123",
            order_index=1,
            shot="Original Shot",
            desc="Original Description",
            duration="5s",
            type="image"
        )
        
        update_request = UpdateStoryboardRequest(
            shot="Updated Shot",
            duration="10s",
            camera_angle="close-up"
        )
        
        updated = StoryboardMapper.update_entity(entity, update_request)
        
        assert updated.shot == "Updated Shot"
        assert updated.duration == "10s"
        assert updated.camera_angle == "close-up"
        assert updated.desc == "Original Description"  # unchanged
    
    def test_roundtrip_conversion(self):
        """Test roundtrip conversion for storyboard"""
        request = CreateStoryboardRequest(
            episode_id="ep_123",
            order=1,
            shot="Shot 1",
            desc="Opening scene",
            duration="5s",
            type="image",
            camera_angle="wide"
        )
        
        entity = StoryboardMapper.to_entity(request, project_id="proj_123")
        schema = StoryboardMapper.to_schema(entity)
        
        assert schema.episode_id == request.episode_id
        assert schema.order == request.order
        assert schema.shot == request.shot
        assert schema.camera_angle == request.camera_angle


class TestTaskMapper:
    """Test TaskMapper conversion correctness"""
    
    def test_to_entity(self):
        """Test creating TaskDB entity from parameters"""
        entity = TaskMapper.to_entity(
            task_type="image",
            model="flux-dev",
            params={"prompt": "test"},
            status="pending",
            user_id="user_123",
            project_id="proj_123",
            max_retries=5
        )
        
        assert entity.type == "image"
        assert entity.model == "flux-dev"
        assert entity.params == {"prompt": "test"}
        assert entity.status == "pending"
        assert entity.user_id == "user_123"
        assert entity.project_id == "proj_123"
        assert entity.max_retries == 5
        assert entity.retry_count == 0
        assert entity.id is not None
    
    def test_to_entity_with_custom_id(self):
        """Test creating TaskDB with custom ID"""
        custom_id = str(uuid.uuid4())
        
        entity = TaskMapper.to_entity(
            task_type="video",
            model="kling-v1",
            params={"prompt": "test"},
            task_id=custom_id
        )
        
        assert entity.id == custom_id
    
    def test_to_schema(self):
        """Test converting TaskDB to Task schema"""
        now = datetime.now().timestamp()
        entity = TaskDB(
            id="task_123",
            type="image",
            status="success",
            created_at=now,
            updated_at=now,
            model="flux-dev",
            params={"prompt": "test"},
            provider_task_id="provider_123",
            result={"url": "https://example.com/image.png"},
            retry_count=1,
            max_retries=3,
            started_at=now,
            completed_at=now + 10,
            user_id="user_123",
            project_id="proj_123"
        )
        
        schema = TaskMapper.to_schema(entity)
        
        assert schema.id == "task_123"
        assert schema.type == "image"
        assert schema.status == "success"
        assert schema.model == "flux-dev"
        assert schema.provider_task_id == "provider_123"
        assert schema.result["url"] == "https://example.com/image.png"
        assert schema.retry_count == 1
        assert schema.user_id == "user_123"
    
    def test_update_status_to_processing(self):
        """Test updating task status to processing"""
        entity = TaskDB(
            type="image",
            status="pending",
            model="flux-dev",
            params={"prompt": "test"}
        )
        
        updated = TaskMapper.update_status(
            entity,
            status="processing",
            provider_task_id="provider_123"
        )
        
        assert updated.status == "processing"
        assert updated.provider_task_id == "provider_123"
        assert updated.started_at is not None
        assert updated.completed_at is None
    
    def test_update_status_to_success(self):
        """Test updating task status to success"""
        entity = TaskDB(
            type="image",
            status="processing",
            model="flux-dev",
            params={"prompt": "test"}
        )
        
        result = {"url": "https://example.com/image.png"}
        updated = TaskMapper.update_status(
            entity,
            status="success",
            result=result
        )
        
        assert updated.status == "success"
        assert updated.result == result
        assert updated.completed_at is not None
    
    def test_update_status_to_failed(self):
        """Test updating task status to failed"""
        entity = TaskDB(
            type="image",
            status="processing",
            model="flux-dev",
            params={"prompt": "test"}
        )
        
        updated = TaskMapper.update_status(
            entity,
            status="failed",
            error="Generation failed"
        )
        
        assert updated.status == "failed"
        assert updated.error == "Generation failed"
        assert updated.completed_at is not None
    
    def test_increment_retry(self):
        """Test incrementing retry count"""
        entity = TaskDB(
            type="image",
            status="failed",
            model="flux-dev",
            params={"prompt": "test"},
            retry_count=0
        )
        
        updated = TaskMapper.increment_retry(entity)
        
        assert updated.retry_count == 1
        assert updated.updated_at > entity.created_at
    
    def test_multiple_retry_increments(self):
        """Test multiple retry increments"""
        entity = TaskDB(
            type="image",
            status="failed",
            model="flux-dev",
            params={"prompt": "test"},
            retry_count=0,
            max_retries=3
        )
        
        # Increment 3 times
        for i in range(3):
            entity = TaskMapper.increment_retry(entity)
            assert entity.retry_count == i + 1
        
        assert entity.retry_count == 3
        assert entity.retry_count == entity.max_retries


class TestCanvasMetadataMapper:
    """Test CanvasMetadataMapper conversion correctness"""
    
    def test_to_entity_general_canvas(self):
        """Test creating general canvas metadata entity"""
        from src.models.schemas import CreateGeneralCanvasRequest
        
        request = CreateGeneralCanvasRequest(
            name="Main Canvas",
            description="Main project canvas"
        )
        
        entity = CanvasMetadataMapper.to_entity(
            schema=request,
            project_id="proj_123"
        )
        
        assert entity.project_id == "proj_123"
        assert entity.canvas_type == "general"
        assert entity.name == "Main Canvas"
        assert entity.description == "Main project canvas"
        assert entity.order_index == 0
        assert entity.is_pinned is False
        assert entity.related_entity_type is None
        assert entity.related_entity_id is None
    
    def test_create_asset_canvas(self):
        """Test creating asset canvas metadata entity"""
        entity = CanvasMetadataMapper.create_asset_canvas(
            project_id="proj_123",
            asset_id="asset_123",
            asset_name="Hero"
        )
        
        assert entity.project_id == "proj_123"
        assert entity.canvas_type == "asset"
        assert entity.related_entity_type == "asset"
        assert entity.related_entity_id == "asset_123"
        assert entity.name == "Hero Canvas"
    
    def test_create_storyboard_canvas(self):
        """Test creating storyboard canvas metadata entity"""
        entity = CanvasMetadataMapper.create_storyboard_canvas(
            project_id="proj_123",
            storyboard_id="sb_123",
            storyboard_shot="Shot 1"
        )
        
        assert entity.project_id == "proj_123"
        assert entity.canvas_type == "storyboard"
        assert entity.related_entity_type == "storyboard"
        assert entity.related_entity_id == "sb_123"
        assert entity.name == "Shot 1 Canvas"
    
    def test_to_schema(self):
        """Test converting CanvasMetadataDB to schema"""
        now = datetime.now().timestamp()
        entity = CanvasMetadataDB(
            id="canvas_123",
            project_id="proj_123",
            canvas_type="general",
            name="Main Canvas",
            description="Main project canvas",
            order_index=0,
            is_pinned=True,
            tags=["main", "primary"],
            node_count=5,
            last_accessed_at=now,
            access_count=10,
            created_at=now,
            updated_at=now
        )
        
        schema = CanvasMetadataMapper.to_schema(entity)
        
        assert schema.id == "canvas_123"
        assert schema.project_id == "proj_123"
        assert schema.canvas_type == "general"
        assert schema.name == "Main Canvas"
        assert schema.is_pinned is True
        assert len(schema.tags) == 2
        assert schema.node_count == 5
        assert schema.access_count == 10
    
    def test_update_entity(self):
        """Test updating canvas metadata"""
        from src.models.schemas import UpdateCanvasMetadataRequest
        
        entity = CanvasMetadataDB(
            project_id="proj_123",
            canvas_type="general",
            name="Original Name",
            description="Original Description",
            order_index=0,
            is_pinned=False,
            tags=["old"]
        )
        
        update_request = UpdateCanvasMetadataRequest(
            name="Updated Name",
            isPinned=True,
            tags=["new", "updated"]
        )
        
        updated = CanvasMetadataMapper.update_entity(entity, update_request)
        
        assert updated.name == "Updated Name"
        assert updated.is_pinned is True
        assert updated.tags == ["new", "updated"]
        assert updated.description == "Original Description"  # unchanged
    
    def test_update_access(self):
        """Test updating canvas access tracking"""
        entity = CanvasMetadataDB(
            project_id="proj_123",
            canvas_type="general",
            name="Main Canvas",
            order_index=0,
            is_pinned=False,
            access_count=5
        )
        
        initial_access_count = entity.access_count
        updated = CanvasMetadataMapper.update_access(entity)
        
        assert updated.access_count == initial_access_count + 1
        assert updated.last_accessed_at is not None


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
