"""
Shot Soft Deletion Service

This service handles the soft deletion of shots and all related data including:
- Tasks associated with the shot
- Submissions for those tasks
- Production notes for those tasks
- Task attachments for those tasks
- Reviews for those submissions

All operations are performed within database transactions to ensure atomicity.
"""

from datetime import datetime
from typing import List, Dict, Any, Optional
from sqlalchemy.orm import Session
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy import func

import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from models.shot import Shot
from models.task import Task, Submission, Review, ProductionNote, TaskAttachment
from models.user import User
from models.activity import Activity, ActivityType
from utils.activity import ActivityService


class DeletionInfo:
    """Information about what will be deleted when a shot is soft deleted."""
    
    def __init__(self):
        self.shot_id: int = 0
        self.shot_name: str = ""
        self.episode_name: str = ""
        self.project_id: int = 0
        self.project_name: str = ""
        
        # Counts of items that will be marked as deleted
        self.task_count: int = 0
        self.submission_count: int = 0
        self.attachment_count: int = 0
        self.note_count: int = 0
        self.review_count: int = 0
        
        # File information (preserved, not deleted)
        self.total_file_size: int = 0
        self.file_count: int = 0
        
        # Affected users
        self.affected_users: List[Dict[str, Any]] = []
        
        # Timestamps
        self.last_activity_date: Optional[str] = None
        self.created_at: str = ""


class DeletionResult:
    """Result of a shot soft deletion operation."""
    
    def __init__(self):
        self.success: bool = False
        self.shot_id: int = 0
        self.shot_name: str = ""
        
        # Database update results
        self.marked_deleted_tasks: int = 0
        self.marked_deleted_submissions: int = 0
        self.marked_deleted_attachments: int = 0
        self.marked_deleted_notes: int = 0
        self.marked_deleted_reviews: int = 0
        
        # Timing
        self.operation_duration: float = 0.0
        self.deleted_at: str = ""
        self.deleted_by: int = 0
        
        # Errors
        self.errors: List[str] = []
        self.warnings: List[str] = []


class ShotSoftDeletionService:
    """Service for handling shot soft deletion operations."""
    
    def __init__(self):
        self.activity_service = ActivityService()
    
    def get_deletion_info(self, shot_id: int, db: Session) -> Optional[DeletionInfo]:
        """
        Get information about what will be deleted when a shot is soft deleted.
        
        Args:
            shot_id: ID of the shot to analyze
            db: Database session
            
        Returns:
            DeletionInfo object with counts and affected users, or None if shot not found
        """
        try:
            # Get the shot (only if not already deleted)
            shot = db.query(Shot).filter(
                Shot.id == shot_id,
                Shot.deleted_at.is_(None)
            ).first()
            
            if not shot:
                return None
            
            info = DeletionInfo()
            info.shot_id = shot.id
            info.shot_name = shot.name
            info.episode_name = shot.episode.name if shot.episode else ""
            info.project_id = shot.project_id
            info.project_name = shot.project.name if shot.project else ""
            info.created_at = shot.created_at.isoformat() if shot.created_at else ""
            
            # Get all active tasks for this shot
            tasks = db.query(Task).filter(
                Task.shot_id == shot_id,
                Task.deleted_at.is_(None)
            ).all()
            
            info.task_count = len(tasks)
            
            if not tasks:
                return info
            
            task_ids = [task.id for task in tasks]
            
            # Count submissions
            submissions = db.query(Submission).filter(
                Submission.task_id.in_(task_ids),
                Submission.deleted_at.is_(None)
            ).all()
            info.submission_count = len(submissions)
            
            # Count attachments and calculate file sizes
            attachments = db.query(TaskAttachment).filter(
                TaskAttachment.task_id.in_(task_ids),
                TaskAttachment.deleted_at.is_(None)
            ).all()
            info.attachment_count = len(attachments)
            info.file_count = len(attachments) + len(submissions)
            info.total_file_size = sum(att.file_size for att in attachments if att.file_size)
            
            # Count production notes
            notes = db.query(ProductionNote).filter(
                ProductionNote.task_id.in_(task_ids),
                ProductionNote.deleted_at.is_(None)
            ).all()
            info.note_count = len(notes)
            
            # Count reviews
            if submissions:
                submission_ids = [sub.id for sub in submissions]
                reviews = db.query(Review).filter(
                    Review.submission_id.in_(submission_ids),
                    Review.deleted_at.is_(None)
                ).all()
                info.review_count = len(reviews)
            
            # Get affected users
            info.affected_users = self._get_affected_users(tasks, submissions, notes, db)
            
            # Get last activity date
            info.last_activity_date = self._get_last_activity_date(shot_id, task_ids, db)
            
            return info
            
        except SQLAlchemyError as e:
            raise Exception(f"Database error while getting deletion info: {str(e)}")
    
    def soft_delete_shot_cascade(self, shot_id: int, db: Session, current_user: User) -> DeletionResult:
        """
        Perform cascading soft deletion of a shot and all related data.
        
        Args:
            shot_id: ID of the shot to soft delete
            db: Database session
            current_user: User performing the deletion
            
        Returns:
            DeletionResult with operation details
        """
        start_time = datetime.utcnow()
        result = DeletionResult()
        result.shot_id = shot_id
        result.deleted_by = current_user.id
        
        try:
            # Get the shot (only if not already deleted)
            shot = db.query(Shot).filter(
                Shot.id == shot_id,
                Shot.deleted_at.is_(None)
            ).first()
            
            if not shot:
                result.errors.append("Shot not found or already deleted")
                return result
            
            result.shot_name = shot.name
            deleted_at = datetime.utcnow()
            result.deleted_at = deleted_at.isoformat()
            
            # Get deletion info for logging
            deletion_info = self.get_deletion_info(shot_id, db)
            
            # Mark related data as deleted
            self._mark_related_data_deleted(shot_id, db, current_user, deleted_at, result)
            
            # Mark the shot as deleted
            shot.deleted_at = deleted_at
            shot.deleted_by = current_user.id
            db.flush()
            
            # Log the deletion activity
            self._log_shot_deletion(shot, current_user, deletion_info, db)
            
            result.success = True
                
        except SQLAlchemyError as e:
            result.errors.append(f"Database error during deletion: {str(e)}")
        except Exception as e:
            result.errors.append(f"Unexpected error during deletion: {str(e)}")
        
        # Calculate operation duration
        end_time = datetime.utcnow()
        result.operation_duration = (end_time - start_time).total_seconds()
        
        return result
    
    def _mark_related_data_deleted(self, shot_id: int, db: Session, current_user: User, 
                                 deleted_at: datetime, result: DeletionResult) -> None:
        """
        Mark all data related to a shot as deleted.
        
        Args:
            shot_id: ID of the shot
            db: Database session
            current_user: User performing the deletion
            deleted_at: Timestamp for deletion
            result: DeletionResult to update with counts
        """
        # Get all active tasks for this shot
        tasks = db.query(Task).filter(
            Task.shot_id == shot_id,
            Task.deleted_at.is_(None)
        ).all()
        
        if not tasks:
            return
        
        task_ids = [task.id for task in tasks]
        
        # Mark tasks as deleted
        task_update_count = db.query(Task).filter(
            Task.shot_id == shot_id,
            Task.deleted_at.is_(None)
        ).update({
            Task.deleted_at: deleted_at,
            Task.deleted_by: current_user.id
        }, synchronize_session=False)
        result.marked_deleted_tasks = task_update_count
        
        # Mark submissions as deleted
        submission_update_count = db.query(Submission).filter(
            Submission.task_id.in_(task_ids),
            Submission.deleted_at.is_(None)
        ).update({
            Submission.deleted_at: deleted_at,
            Submission.deleted_by: current_user.id
        }, synchronize_session=False)
        result.marked_deleted_submissions = submission_update_count
        
        # Mark attachments as deleted
        attachment_update_count = db.query(TaskAttachment).filter(
            TaskAttachment.task_id.in_(task_ids),
            TaskAttachment.deleted_at.is_(None)
        ).update({
            TaskAttachment.deleted_at: deleted_at,
            TaskAttachment.deleted_by: current_user.id
        }, synchronize_session=False)
        result.marked_deleted_attachments = attachment_update_count
        
        # Mark production notes as deleted
        note_update_count = db.query(ProductionNote).filter(
            ProductionNote.task_id.in_(task_ids),
            ProductionNote.deleted_at.is_(None)
        ).update({
            ProductionNote.deleted_at: deleted_at,
            ProductionNote.deleted_by: current_user.id
        }, synchronize_session=False)
        result.marked_deleted_notes = note_update_count
        
        # Mark reviews as deleted (for submissions that belong to these tasks)
        # First get submission IDs
        submission_ids = [sub.id for sub in db.query(Submission.id).filter(
            Submission.task_id.in_(task_ids)
        ).all()]
        
        if submission_ids:
            review_update_count = db.query(Review).filter(
                Review.submission_id.in_(submission_ids),
                Review.deleted_at.is_(None)
            ).update({
                Review.deleted_at: deleted_at,
                Review.deleted_by: current_user.id
            }, synchronize_session=False)
            result.marked_deleted_reviews = review_update_count
        
        db.flush()
    
    def _get_affected_users(self, tasks: List[Task], submissions: List[Submission], 
                          notes: List[ProductionNote], db: Session) -> List[Dict[str, Any]]:
        """
        Get list of users affected by the shot deletion.
        
        Args:
            tasks: List of tasks that will be deleted
            submissions: List of submissions that will be deleted
            notes: List of production notes that will be deleted
            db: Database session
            
        Returns:
            List of affected user information
        """
        user_data = {}
        
        # Collect user IDs and their involvement
        for task in tasks:
            if task.assigned_user_id:
                if task.assigned_user_id not in user_data:
                    user_data[task.assigned_user_id] = {
                        'task_count': 0,
                        'submission_count': 0,
                        'note_count': 0,
                        'last_activity_date': None
                    }
                user_data[task.assigned_user_id]['task_count'] += 1
                if task.updated_at:
                    current_date = user_data[task.assigned_user_id]['last_activity_date']
                    if not current_date or task.updated_at > current_date:
                        user_data[task.assigned_user_id]['last_activity_date'] = task.updated_at
        
        for submission in submissions:
            if submission.user_id not in user_data:
                user_data[submission.user_id] = {
                    'task_count': 0,
                    'submission_count': 0,
                    'note_count': 0,
                    'last_activity_date': None
                }
            user_data[submission.user_id]['submission_count'] += 1
            if submission.submitted_at:
                current_date = user_data[submission.user_id]['last_activity_date']
                if not current_date or submission.submitted_at > current_date:
                    user_data[submission.user_id]['last_activity_date'] = submission.submitted_at
        
        for note in notes:
            if note.user_id not in user_data:
                user_data[note.user_id] = {
                    'task_count': 0,
                    'submission_count': 0,
                    'note_count': 0,
                    'last_activity_date': None
                }
            user_data[note.user_id]['note_count'] += 1
            if note.updated_at:
                current_date = user_data[note.user_id]['last_activity_date']
                if not current_date or note.updated_at > current_date:
                    user_data[note.user_id]['last_activity_date'] = note.updated_at
        
        # Get user details
        affected_users = []
        if user_data:
            users = db.query(User).filter(User.id.in_(user_data.keys())).all()
            for user in users:
                data = user_data[user.id]
                affected_users.append({
                    'id': user.id,
                    'name': f"{user.first_name} {user.last_name}",
                    'email': user.email,
                    'role': user.role,
                    'task_count': data['task_count'],
                    'submission_count': data['submission_count'],
                    'note_count': data['note_count'],
                    'last_activity_date': data['last_activity_date'].isoformat() if data['last_activity_date'] else None
                })
        
        return affected_users
    
    def _get_last_activity_date(self, shot_id: int, task_ids: List[int], db: Session) -> Optional[str]:
        """
        Get the most recent activity date for the shot and its tasks.
        
        Args:
            shot_id: ID of the shot
            task_ids: List of task IDs
            db: Database session
            
        Returns:
            ISO formatted date string of last activity, or None
        """
        try:
            # Get the most recent activity from various sources
            dates = []
            
            # Shot updated_at
            shot = db.query(Shot.updated_at).filter(Shot.id == shot_id).first()
            if shot and shot.updated_at:
                dates.append(shot.updated_at)
            
            if task_ids:
                # Task updated_at
                task_dates = db.query(func.max(Task.updated_at)).filter(
                    Task.id.in_(task_ids)
                ).scalar()
                if task_dates:
                    dates.append(task_dates)
                
                # Submission submitted_at
                submission_dates = db.query(func.max(Submission.submitted_at)).filter(
                    Submission.task_id.in_(task_ids)
                ).scalar()
                if submission_dates:
                    dates.append(submission_dates)
                
                # Production note updated_at
                note_dates = db.query(func.max(ProductionNote.updated_at)).filter(
                    ProductionNote.task_id.in_(task_ids)
                ).scalar()
                if note_dates:
                    dates.append(note_dates)
            
            if dates:
                return max(dates).isoformat()
            
            return None
            
        except SQLAlchemyError:
            return None
    
    def _log_shot_deletion(self, shot: Shot, current_user: User, deletion_info: Optional[DeletionInfo], db: Session) -> None:
        """
        Log the shot deletion activity.
        
        Args:
            shot: The shot that was deleted
            current_user: User who performed the deletion
            deletion_info: Information about what was deleted
            db: Database session
        """
        try:
            # Create activity record
            activity = Activity(
                type=ActivityType.SHOT_UPDATED,  # We'll use SHOT_UPDATED for now, could add SHOT_DELETED later
                user_id=current_user.id,
                project_id=shot.project_id,
                shot_id=shot.id,
                description=f"Shot '{shot.name}' was deleted by {current_user.first_name} {current_user.last_name}",
                activity_metadata={
                    'action': 'soft_delete',
                    'shot_name': shot.name,
                    'episode_name': shot.episode.name if shot.episode else None,
                    'project_id': shot.project_id,
                    'project_name': shot.project.name if shot.project else None,
                    'deleted_counts': {
                        'tasks': deletion_info.task_count if deletion_info else 0,
                        'submissions': deletion_info.submission_count if deletion_info else 0,
                        'attachments': deletion_info.attachment_count if deletion_info else 0,
                        'notes': deletion_info.note_count if deletion_info else 0,
                        'reviews': deletion_info.review_count if deletion_info else 0
                    },
                    'affected_users_count': len(deletion_info.affected_users) if deletion_info else 0
                }
            )
            
            db.add(activity)
            db.flush()
            
        except SQLAlchemyError as e:
            # Don't fail the deletion if logging fails, just add a warning
            pass