from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import List

from database import get_db
from models.shot import Shot
from models.episode import Episode
from models.project import Project, ProjectMember
from models.task import Task, TaskType, TaskStatus
from models.user import User, UserRole
from schemas.shot import (
    ShotCreate, ShotUpdate, ShotResponse, ShotListResponse,
    BulkShotCreate, BulkShotResponse, TaskStatusInfo
)
from utils.auth import get_current_user_from_token
from services.shot_soft_deletion import ShotSoftDeletionService

router = APIRouter()


def get_current_user_with_db(
    token_data: dict = Depends(get_current_user_from_token),
    db: Session = Depends(get_db)
):
    """Get current user with proper database dependency."""
    from utils.auth import _get_user_from_db
    return _get_user_from_db(db, token_data["user_id"])


def require_coordinator_or_admin(
    token_data: dict = Depends(get_current_user_from_token),
    db: Session = Depends(get_db)
):
    """Require coordinator or admin role."""
    from utils.auth import _get_user_from_db
    current_user = _get_user_from_db(db, token_data["user_id"])
    
    if current_user.role != UserRole.COORDINATOR and not current_user.is_admin:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Insufficient permissions"
        )
    return current_user


def check_episode_access(episode_id: int, current_user: User, db: Session):
    """Check if user has access to the episode and its project."""
    # Debug logging
    print(f"[DEBUG] check_episode_access called")
    print(f"[DEBUG] User: {current_user.email}, Role: {current_user.role}, is_admin: {current_user.is_admin}")
    
    # Check if episode exists
    episode = db.query(Episode).filter(Episode.id == episode_id).first()
    if not episode:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Episode not found"
        )
    
    # Admins and coordinators have access to all episodes
    if current_user.is_admin or current_user.role == UserRole.COORDINATOR:
        print(f"[DEBUG] Access GRANTED - Admin or Coordinator")
        return episode
    
    # Check project access for artists and other roles
    if current_user.role == UserRole.ARTIST:
        print(f"[DEBUG] Checking project membership for artist")
        member = db.query(ProjectMember).filter(
            ProjectMember.project_id == episode.project_id,
            ProjectMember.user_id == current_user.id
        ).first()
        if not member:
            print(f"[DEBUG] Access DENIED - Not a project member")
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Access denied to this project"
            )
        print(f"[DEBUG] Access GRANTED - Project member")
    
    return episode


def get_status_sort_order(status: str, project_custom_statuses: list = None) -> int:
    """Get sort order for task status, including custom statuses."""
    # Default system status order
    system_status_order = {
        "not_started": 0,
        "in_progress": 1,
        "submitted": 2,
        "retake": 3,
        "approved": 4
    }
    
    # If it's a system status, return its order
    if status in system_status_order:
        return system_status_order[status]
    
    # For custom statuses, use their defined order + offset to place them after system statuses
    if project_custom_statuses:
        for custom_status in project_custom_statuses:
            if isinstance(custom_status, dict) and custom_status.get('id') == status:
                # Custom statuses start after system statuses (5+)
                return 5 + custom_status.get('order', 0)
    
    # Unknown status defaults to 0 (same as not_started)
    return 0


def get_project_custom_statuses(project_id: int, db: Session) -> list:
    """Get custom task statuses for a project."""
    project = db.query(Project).filter(Project.id == project_id).first()
    if not project or not project.custom_task_statuses:
        return []
    
    custom_statuses_data = project.custom_task_statuses
    if isinstance(custom_statuses_data, str):
        try:
            import json
            custom_statuses_data = json.loads(custom_statuses_data)
        except (json.JSONDecodeError, TypeError):
            return []
    
    return custom_statuses_data if isinstance(custom_statuses_data, list) else []


# Standard shot task types (read-only)
STANDARD_SHOT_TASK_TYPES = ["layout", "animation", "simulation", "lighting", "compositing"]


def get_default_shot_task_types():
    """Get default task types for shots."""
    return [
        TaskType.LAYOUT.value,
        TaskType.ANIMATION.value,
        TaskType.LIGHTING.value,
        TaskType.COMPOSITING.value
    ]


def get_all_shot_task_types(project_id: int, db: Session) -> List[str]:
    """Get all task types (standard + custom) for shots in a project."""
    project = db.query(Project).filter(Project.id == project_id).first()
    if not project:
        return STANDARD_SHOT_TASK_TYPES
    
    custom_types = project.custom_shot_task_types or []
    return STANDARD_SHOT_TASK_TYPES + custom_types


def create_default_tasks_for_shot(shot: Shot, task_types: List[str], db: Session):
    """Create default tasks for a shot."""
    created_tasks = []
    
    for task_type in task_types:
        task_name = f"{shot.name}_{task_type}"
        task_description = f"{task_type.title()} task for shot {shot.name}"
        
        task = Task(
            project_id=shot.project_id,
            episode_id=shot.episode_id,
            shot_id=shot.id,
            task_type=task_type,
            name=task_name,
            description=task_description
        )
        
        db.add(task)
        created_tasks.append(task)
    
    return created_tasks


@router.get("/", response_model=List[ShotListResponse])
async def list_shots(
    episode_id: int = None,
    project_id: int = None,
    task_status_filter: str = None,
    sort_by: str = None,
    sort_direction: str = "asc",
    skip: int = 0,
    limit: int = 100,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user_with_db)
):
    """List shots with optional filtering by episode, project and task status"""
    from sqlalchemy import func, case
    from sqlalchemy.orm import selectinload, joinedload
    
    # Build base query for shots (exclude soft deleted)
    base_query = db.query(Shot).filter(Shot.deleted_at.is_(None))
    
    # Filter by project_id if specified
    if project_id:
        # Check project access for artists
        if current_user.role == UserRole.ARTIST:
            member = db.query(ProjectMember).filter(
                ProjectMember.project_id == project_id,
                ProjectMember.user_id == current_user.id
            ).first()
            if not member and not current_user.is_admin:
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail="Access denied to this project"
                )
        base_query = base_query.filter(Shot.project_id == project_id)
    
    # Filter by episode if specified
    if episode_id:
        check_episode_access(episode_id, current_user, db)
        base_query = base_query.filter(Shot.episode_id == episode_id)
    elif not project_id:
        # If no episode or project specified, filter by user's accessible projects for artists
        if current_user.role == UserRole.ARTIST:
            accessible_projects = db.query(ProjectMember.project_id).filter(
                ProjectMember.user_id == current_user.id
            ).subquery()
            base_query = base_query.filter(Shot.project_id.in_(accessible_projects))
    
    # Apply sorting if specified (for non-task-status fields)
    if sort_by and not sort_by.endswith('_status'):
        if sort_by in ['name', 'status', 'frame_start', 'frame_end', 'created_at', 'updated_at']:
            sort_column = getattr(Shot, sort_by)
            if sort_direction.lower() == 'desc':
                base_query = base_query.order_by(sort_column.desc())
            else:
                base_query = base_query.order_by(sort_column.asc())
    
    # OPTIMIZATION: Use single query with optimized JOIN to fetch shots and their tasks
    # This replaces the N+1 query pattern with a single database operation
    shots_with_tasks = (
        base_query
        .outerjoin(Task, (Task.shot_id == Shot.id) & (Task.deleted_at.is_(None)))
        .options(
            joinedload(Shot.episode).joinedload(Episode.project),  # Eager load episode and project
            selectinload(Shot.tasks).options(  # Use selectinload for better performance with tasks
                selectinload(Task.assigned_user)  # Eager load assigned users
            )
        )
        .add_columns(
            Task.id.label('task_id'),
            Task.task_type,
            Task.status.label('task_status'),
            Task.assigned_user_id,
            Task.updated_at.label('task_updated_at')  # Include task update time for better tracking
        )
        .offset(skip)
        .limit(limit)
        .all()
    )
    
    # OPTIMIZATION: Pre-fetch all project data and task types in a single query
    # This eliminates the need for repeated project queries
    project_ids = set()
    for row in shots_with_tasks:
        shot = row[0]
        if shot.project_id not in project_ids:
            project_ids.add(shot.project_id)
    
    # Get all projects with their custom task types in one optimized query
    project_data = {}
    if project_ids:
        projects = (
            db.query(Project)
            .filter(Project.id.in_(project_ids))
            .all()
        )
        for project in projects:
            custom_types = project.custom_shot_task_types or []
            raw = project.custom_task_statuses
            if isinstance(raw, str):
                try:
                    import json
                    raw = json.loads(raw)
                except (json.JSONDecodeError, TypeError):
                    raw = []
            custom_statuses = raw if isinstance(raw, list) else []
            project_data[project.id] = {
                'task_types': STANDARD_SHOT_TASK_TYPES + custom_types,
                'custom_statuses': custom_statuses
            }
    
    # OPTIMIZATION: Group results by shot and aggregate task data efficiently
    shots_dict = {}
    for row in shots_with_tasks:
        shot = row[0]  # Shot object
        task_id = row[1]  # task_id
        task_type = row[2]  # task_type
        task_status = row[3]  # task_status
        assigned_user_id = row[4]  # assigned_user_id
        task_updated_at = row[5]  # task_updated_at
        
        if shot.id not in shots_dict:
            # Initialize shot data with pre-fetched project data
            project_info = project_data.get(shot.project_id, {
                'task_types': STANDARD_SHOT_TASK_TYPES,
                'custom_statuses': []
            })
            
            shots_dict[shot.id] = {
                'shot': shot,
                'tasks': [],
                'task_status': {},
                'task_details': [],
                'project_info': project_info
            }
            
            # Initialize all task types as not started using pre-fetched data
            for task_type_init in project_info['task_types']:
                shots_dict[shot.id]['task_status'][task_type_init] = "not_started"
        
        # Add task data if task exists
        if task_id is not None:
            shots_dict[shot.id]['tasks'].append({
                'task_id': task_id,
                'task_type': task_type,
                'status': task_status,
                'assigned_user_id': assigned_user_id,
                'updated_at': task_updated_at
            })
            
            # Update task status
            shots_dict[shot.id]['task_status'][task_type] = task_status
            
            # Add to task details with enhanced information
            shots_dict[shot.id]['task_details'].append(TaskStatusInfo(
                task_type=task_type,
                status=task_status,
                task_id=task_id,
                assigned_user_id=assigned_user_id
            ))
    
    # Build response list efficiently
    result = []
    for shot_data in shots_dict.values():
        shot = shot_data['shot']
        
        # Create shot response with optimized data
        shot_response = ShotListResponse.model_validate(shot)
        shot_response.task_count = len(shot_data['tasks'])
        shot_response.task_status = shot_data['task_status']
        shot_response.task_details = shot_data['task_details']
        
        result.append(shot_response)
    
    # Apply task status filtering if specified
    if task_status_filter:
        try:
            # Parse task status filter (format: "task_type:status")
            task_type, status = task_status_filter.split(":")
            filter_status = status  # Use string directly instead of enum
            result = [
                shot for shot in result 
                if shot.task_status.get(task_type) == filter_status
            ]
        except (ValueError, KeyError):
            # Invalid filter format, ignore
            pass
    
    # Apply task status sorting if specified using pre-fetched custom status data
    if sort_by and sort_by.endswith('_status'):
        task_type = sort_by.replace('_status', '')
        
        def get_status_order(shot):
            status = shot.task_status.get(task_type, "not_started")
            # Use pre-fetched custom statuses from shots_dict
            shot_id = shot.id
            if shot_id in shots_dict:
                custom_statuses = shots_dict[shot_id]['project_info']['custom_statuses']
                return get_status_sort_order(status, custom_statuses)
            return get_status_sort_order(status, [])
        
        reverse = sort_direction.lower() == 'desc'
        result.sort(key=get_status_order, reverse=reverse)
    
    return result


@router.post("/", response_model=ShotResponse, status_code=status.HTTP_201_CREATED)
async def create_shot(
    shot: ShotCreate,
    episode_id: int,
    create_default_tasks: bool = True,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_coordinator_or_admin)
):
    """Create a new shot in an episode"""
    # Check episode access
    episode = check_episode_access(episode_id, current_user, db)
    
    # Auto-populate project_id from episode if not provided
    if shot.project_id is None:
        shot.project_id = episode.project_id
    else:
        # Validate that provided project_id matches episode's project
        if shot.project_id != episode.project_id:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Project ID must match the episode's project"
            )
    
    # Validate frame range
    if shot.frame_end < shot.frame_start:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Frame end must be greater than or equal to frame start"
        )
    
    # Check if shot name already exists in project (project-scoped uniqueness)
    existing_shot = db.query(Shot).filter(
        Shot.project_id == shot.project_id,
        Shot.name == shot.name,
        Shot.deleted_at.is_(None)
    ).first()
    
    if existing_shot:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Shot with this name already exists in the project"
        )
    
    # Create new shot
    db_shot = Shot(
        episode_id=episode_id,
        project_id=shot.project_id,
        **shot.model_dump(exclude={'project_id'})
    )
    
    db.add(db_shot)
    db.commit()
    db.refresh(db_shot)
    
    # Create default tasks if requested
    task_count = 0
    if create_default_tasks:
        # Get all task types (standard + custom) for this project
        all_task_types = get_all_shot_task_types(episode.project_id, db)
        # Use default standard types for now (can be customized via project settings)
        default_task_types = get_default_shot_task_types()
        created_tasks = create_default_tasks_for_shot(db_shot, default_task_types, db)
        db.commit()
        task_count = len(created_tasks)
    
    # Return response with task count
    shot_data = ShotResponse.model_validate(db_shot)
    shot_data.task_count = task_count
    
    return shot_data


@router.post("/bulk", response_model=BulkShotResponse, status_code=status.HTTP_201_CREATED)
async def create_shots_bulk(
    bulk_shot: BulkShotCreate,
    episode_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_coordinator_or_admin)
):
    """Create multiple shots with naming pattern and default tasks"""
    # Check episode access
    episode = check_episode_access(episode_id, current_user, db)
    
    # Auto-populate project_id from episode - all shots in bulk operation belong to same project
    project_id = episode.project_id
    
    # Validate frame range
    if bulk_shot.frame_end < bulk_shot.frame_start:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Frame end must be greater than or equal to frame start"
        )
    
    # Determine task types to create
    if bulk_shot.task_types:
        # Validate that all selected task types are valid (standard or custom)
        all_valid_types = get_all_shot_task_types(episode.project_id, db)
        invalid_types = [t for t in bulk_shot.task_types if t not in all_valid_types]
        if invalid_types:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail=f"Invalid task types: {', '.join(invalid_types)}"
            )
        task_types = bulk_shot.task_types
    else:
        task_types = get_default_shot_task_types()
    
    # Pre-validate all shot names for project-scoped uniqueness before creating any shots
    shot_names_to_create = []
    for i in range(bulk_shot.shot_count):
        shot_number = bulk_shot.start_number + i
        shot_name = f"{bulk_shot.name_prefix}{shot_number:0{bulk_shot.number_padding}d}"
        shot_names_to_create.append(shot_name)
    
    # Check for existing shots with any of the names in this project
    existing_shots = db.query(Shot.name).filter(
        Shot.project_id == project_id,
        Shot.name.in_(shot_names_to_create),
        Shot.deleted_at.is_(None)
    ).all()
    
    if existing_shots:
        existing_names = [shot.name for shot in existing_shots]
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"The following shot names already exist in project {project_id}: {', '.join(existing_names)}"
        )
    
    # Check for duplicate names within the bulk creation itself
    if len(shot_names_to_create) != len(set(shot_names_to_create)):
        duplicates = [name for name in shot_names_to_create if shot_names_to_create.count(name) > 1]
        unique_duplicates = list(set(duplicates))
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=f"Duplicate shot names in bulk creation: {', '.join(unique_duplicates)}"
        )
    
    created_shots = []
    total_tasks_created = 0
    
    try:
        # Create all shots - validation already done above
        for i, shot_name in enumerate(shot_names_to_create):
            shot_number = bulk_shot.start_number + i
            
            # Create description from template
            description = None
            if bulk_shot.description_template:
                description = bulk_shot.description_template.replace("{shot_name}", shot_name).replace("{shot_number}", str(shot_number))
            
            # Create shot - project consistency guaranteed by using episode's project_id
            db_shot = Shot(
                project_id=project_id,
                episode_id=episode_id,
                name=shot_name,
                description=description,
                frame_start=bulk_shot.frame_start,
                frame_end=bulk_shot.frame_end
            )
            
            db.add(db_shot)
            db.flush()  # Flush to get the shot ID
            
            # Create default tasks if requested
            task_count = 0
            if bulk_shot.create_default_tasks:
                created_tasks = create_default_tasks_for_shot(db_shot, task_types, db)
                task_count = len(created_tasks)
                total_tasks_created += task_count
            
            # Add to response list
            shot_data = ShotResponse.model_validate(db_shot)
            shot_data.task_count = task_count
            created_shots.append(shot_data)
        
        # Commit all changes
        db.commit()
        
        message = f"Successfully created {len(created_shots)} shots in project {project_id}"
        if total_tasks_created > 0:
            message += f" with {total_tasks_created} tasks"
        
        return BulkShotResponse(
            created_shots=created_shots,
            created_tasks_count=total_tasks_created,
            message=message
        )
        
    except Exception as e:
        db.rollback()
        if isinstance(e, HTTPException):
            raise e
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Error creating shots: {str(e)}"
        )


@router.get("/{shot_id}", response_model=ShotResponse)
async def get_shot(
    shot_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(get_current_user_with_db)
):
    """Get a specific shot by ID"""
    from sqlalchemy.orm import joinedload, selectinload
    
    # OPTIMIZATION: Use single query with optimized JOINs to fetch shot and all related data
    # This replaces separate queries with a single database operation
    shot_query = (
        db.query(Shot)
        .options(
            joinedload(Shot.episode).joinedload(Episode.project),  # Eager load episode and project
            selectinload(Shot.tasks).options(  # Use selectinload for better performance with tasks
                selectinload(Task.assigned_user)  # Eager load assigned users if needed
            )
        )
        .filter(Shot.id == shot_id, Shot.deleted_at.is_(None))
    )
    
    shot = shot_query.first()
    
    if not shot:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Shot not found"
        )
    
    # Check episode access
    check_episode_access(shot.episode_id, current_user, db)
    
    # OPTIMIZATION: Count tasks from the already loaded relationship
    # This avoids a separate COUNT query
    active_tasks = [task for task in shot.tasks if task.deleted_at is None]
    task_count = len(active_tasks)
    
    shot_data = ShotResponse.model_validate(shot)
    shot_data.task_count = task_count
    
    # Add project_name from episode.project (already eager loaded)
    if shot.episode and shot.episode.project:
        shot_data.project_name = shot.episode.project.name
    
    # Add task status information (similar to list endpoint)
    project = shot.episode.project if shot.episode else None
    
    # Get all task types: standard + custom
    from routers.shots import STANDARD_SHOT_TASK_TYPES
    project_task_types = list(STANDARD_SHOT_TASK_TYPES)  # Start with standard types
    
    if project and project.custom_shot_task_types:
        custom_types = project.custom_shot_task_types or []
        if isinstance(custom_types, list):
            for ct in custom_types:
                if isinstance(ct, dict) and 'type' in ct:
                    project_task_types.append(ct['type'])
                elif isinstance(ct, str):
                    project_task_types.append(ct)
    
    # Initialize task_status and task_ids dictionaries
    task_status_dict = {}
    task_ids_dict = {}
    task_details_list = []
    
    # Initialize with default not_started for all project task types
    for task_type_init in project_task_types:
        task_status_dict[task_type_init] = "not_started"
    
    # Build task information from active tasks
    for task in active_tasks:
        task_type = task.task_type.value if hasattr(task.task_type, 'value') else task.task_type
        task_status = task.status.value if hasattr(task.status, 'value') else task.status
        task_id = task.id
        assigned_user_id = task.assigned_user_id
        
        # Update task status
        task_status_dict[task_type] = task_status
        task_ids_dict[task_type] = task_id
        
        # Add to task details
        task_details_list.append(TaskStatusInfo(
            task_type=task_type,
            status=task_status,
            task_id=task_id,
            assigned_user_id=assigned_user_id
        ))
    
    shot_data.task_status = task_status_dict
    shot_data.task_ids = task_ids_dict
    shot_data.task_details = task_details_list
    
    return shot_data


@router.post("/{shot_id}/tasks", response_model=TaskStatusInfo, status_code=status.HTTP_201_CREATED)
async def create_shot_task(
    shot_id: int,
    task_type: str,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_coordinator_or_admin)
):
    """Create a new task for a shot"""
    # Exclude soft deleted shots
    shot = db.query(Shot).filter(
        Shot.id == shot_id,
        Shot.deleted_at.is_(None)
    ).first()
    
    if not shot:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Shot not found"
        )
    
    # Check episode access
    episode = check_episode_access(shot.episode_id, current_user, db)
    
    # Check if task already exists (exclude soft deleted)
    existing_task = db.query(Task).filter(
        Task.shot_id == shot_id,
        Task.task_type == task_type,
        Task.deleted_at.is_(None)
    ).first()
    
    if existing_task:
        # Return existing task info instead of error
        return TaskStatusInfo(
            task_type=existing_task.task_type,
            status=existing_task.status,
            task_id=existing_task.id,
            assigned_user_id=existing_task.assigned_user_id
        )
    
    # Create the task
    task_name = f"{shot.name} - {task_type.title()}"
    db_task = Task(
        project_id=shot.project_id,
        episode_id=shot.episode_id,
        shot_id=shot.id,
        task_type=task_type,
        name=task_name,
        description=f"{task_type.title()} task for {shot.name}",
        status="not_started"
    )
    
    db.add(db_task)
    db.commit()
    db.refresh(db_task)
    
    return TaskStatusInfo(
        task_type=db_task.task_type,
        status=db_task.status,
        task_id=db_task.id,
        assigned_user_id=db_task.assigned_user_id
    )


@router.put("/{shot_id}", response_model=ShotResponse)
async def update_shot(
    shot_id: int,
    shot_update: ShotUpdate,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_coordinator_or_admin)
):
    """Update a shot"""
    from sqlalchemy.orm import selectinload
    
    # OPTIMIZATION: Use eager loading to fetch shot with tasks in single query
    db_shot = (
        db.query(Shot)
        .options(selectinload(Shot.tasks))  # Eager load tasks for counting
        .filter(
            Shot.id == shot_id,
            Shot.deleted_at.is_(None)
        )
        .first()
    )
    
    if not db_shot:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Shot not found"
        )
    
    # Check episode access
    check_episode_access(db_shot.episode_id, current_user, db)
    
    # Validate project_id if provided
    if shot_update.project_id is not None:
        if shot_update.project_id != db_shot.project_id:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Cannot change project_id - must match episode's project"
            )
    
    # Validate frame range if both values are provided
    frame_start = shot_update.frame_start if shot_update.frame_start is not None else db_shot.frame_start
    frame_end = shot_update.frame_end if shot_update.frame_end is not None else db_shot.frame_end
    
    if frame_end < frame_start:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Frame end must be greater than or equal to frame start"
        )
    
    # Check if new name conflicts with existing shots in the same project (project-scoped uniqueness)
    if shot_update.name and shot_update.name != db_shot.name:
        existing_shot = db.query(Shot).filter(
            Shot.project_id == db_shot.project_id,
            Shot.name == shot_update.name,
            Shot.id != shot_id,
            Shot.deleted_at.is_(None)
        ).first()
        
        if existing_shot:
            raise HTTPException(
                status_code=status.HTTP_400_BAD_REQUEST,
                detail="Shot with this name already exists in the project"
            )
    
    # Update only provided fields
    update_data = shot_update.model_dump(exclude_unset=True)
    for field, value in update_data.items():
        setattr(db_shot, field, value)
    
    db.commit()
    db.refresh(db_shot)
    
    # OPTIMIZATION: Count tasks using the relationship instead of separate query
    # This avoids an additional database query
    active_tasks = [task for task in db_shot.tasks if task.deleted_at is None]
    task_count = len(active_tasks)
    
    shot_data = ShotResponse.model_validate(db_shot)
    shot_data.task_count = task_count
    
    return shot_data


@router.get("/{shot_id}/deletion-info")
async def get_shot_deletion_info(
    shot_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_coordinator_or_admin)
):
    """Get information about what will be deleted when deleting a shot"""
    # Exclude soft deleted shots
    db_shot = db.query(Shot).filter(
        Shot.id == shot_id,
        Shot.deleted_at.is_(None)
    ).first()
    
    if not db_shot:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Shot not found"
        )
    
    # Check episode access
    check_episode_access(db_shot.episode_id, current_user, db)
    
    # Use the soft deletion service to get comprehensive deletion info
    deletion_service = ShotSoftDeletionService()
    deletion_info = deletion_service.get_deletion_info(shot_id, db)
    
    if not deletion_info:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Shot not found"
        )
    
    return {
        "shot_id": deletion_info.shot_id,
        "shot_name": deletion_info.shot_name,
        "episode_name": deletion_info.episode_name,
        "project_name": deletion_info.project_name,
        "task_count": deletion_info.task_count,
        "submission_count": deletion_info.submission_count,
        "attachment_count": deletion_info.attachment_count,
        "note_count": deletion_info.note_count,
        "review_count": deletion_info.review_count,
        "total_file_size": deletion_info.total_file_size,
        "file_count": deletion_info.file_count,
        "affected_users": deletion_info.affected_users,
        "last_activity_date": deletion_info.last_activity_date,
        "created_at": deletion_info.created_at
    }


@router.delete("/{shot_id}")
async def delete_shot(
    shot_id: int,
    db: Session = Depends(get_db),
    current_user: User = Depends(require_coordinator_or_admin)
):
    """Soft delete a shot and all its associated data"""
    # Exclude soft deleted shots
    db_shot = db.query(Shot).filter(
        Shot.id == shot_id,
        Shot.deleted_at.is_(None)
    ).first()
    
    if not db_shot:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail="Shot not found"
        )
    
    # Check episode access
    check_episode_access(db_shot.episode_id, current_user, db)
    
    # Use the soft deletion service to perform cascading soft deletion
    deletion_service = ShotSoftDeletionService()
    result = deletion_service.soft_delete_shot_cascade(shot_id, db, current_user)
    
    if not result.success:
        db.rollback()
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail={
                "message": "Failed to delete shot",
                "errors": result.errors
            }
        )
    
    # Commit the transaction
    db.commit()
    
    return {
        "message": f"Shot '{result.shot_name}' and all related data have been deleted",
        "shot_id": result.shot_id,
        "shot_name": result.shot_name,
        "deleted_at": result.deleted_at,
        "deleted_by": result.deleted_by,
        "marked_deleted_tasks": result.marked_deleted_tasks,
        "marked_deleted_submissions": result.marked_deleted_submissions,
        "marked_deleted_attachments": result.marked_deleted_attachments,
        "marked_deleted_notes": result.marked_deleted_notes,
        "marked_deleted_reviews": result.marked_deleted_reviews,
        "operation_duration": result.operation_duration
    }