use std::sync::Arc;
use tracing::{debug, error, info, instrument, warn};
use uuid::Uuid;

use crate::application::dtos::display_helpers::{
    category_for, icon_class_for, icon_special_class_for,
};
use crate::application::dtos::trash_dto::TrashedItemDto;
use crate::application::ports::storage_ports::{FileReadPort, FileWritePort};
use crate::application::ports::trash_ports::TrashUseCase;
use crate::common::errors::{DomainError, ErrorKind, Result};
use crate::domain::entities::trashed_item::{TrashedItem, TrashedItemType};
use crate::domain::repositories::folder_repository::FolderRepository;
use crate::domain::repositories::trash_repository::TrashRepository;
use crate::infrastructure::repositories::pg::file_blob_read_repository::FileBlobReadRepository;
use crate::infrastructure::repositories::pg::file_blob_write_repository::FileBlobWriteRepository;
use crate::infrastructure::repositories::pg::folder_db_repository::FolderDbRepository;
use crate::infrastructure::repositories::pg::trash_db_repository::TrashDbRepository;
use crate::infrastructure::services::file_content_cache::FileContentCache;
use crate::infrastructure::services::thumbnail_service::ThumbnailService;

/**
 * Application service for trash operations.
 *
 * The TrashService implements the trash management functionality in the application layer,
 * handling movement of files and folders to trash, restoration from trash, and permanent
 * deletion. It orchestrates interactions between the domain entities and infrastructure
 * repositories while enforcing business rules like retention policies.
 *
 * This service follows the Clean Architecture pattern by:
 * - Depending on application ports rather than domain/infrastructure traits
 * - Orchestrating domain operations without containing domain logic
 * - Exposing its functionality through the TrashUseCase port
 */
pub struct TrashService {
    /// Repository for trash-specific operations like listing and retrieving trashed items
    trash_repository: Arc<TrashDbRepository>,

    /// Port for file read operations (get file metadata)
    file_read_port: Arc<FileBlobReadRepository>,

    /// Port for file write operations (trash, restore, delete)
    file_write_port: Arc<FileBlobWriteRepository>,

    /// Port for folder operations (get folder, trash, restore, delete)
    folder_storage_port: Arc<FolderDbRepository>,

    /// Thumbnail service for cleaning up thumbnails on permanent delete
    thumbnail_service: Option<Arc<ThumbnailService>>,

    /// Content cache — invalidated when files are permanently deleted from trash.
    content_cache: Option<Arc<FileContentCache>>,

    /// Number of days items should be kept in trash before automatic cleanup
    retention_days: u32,
}

impl TrashService {
    pub fn new(
        trash_repository: Arc<TrashDbRepository>,
        file_read_port: Arc<FileBlobReadRepository>,
        file_write_port: Arc<FileBlobWriteRepository>,
        folder_storage_port: Arc<FolderDbRepository>,
        retention_days: u32,
        thumbnail_service: Option<Arc<ThumbnailService>>,
        content_cache: Option<Arc<FileContentCache>>,
    ) -> Self {
        Self {
            trash_repository,
            file_read_port,
            file_write_port,
            folder_storage_port,
            thumbnail_service,
            content_cache,
            retention_days,
        }
    }

    /// Converts a TrashedItem entity to a DTO
    fn to_dto(&self, item: TrashedItem) -> TrashedItemDto {
        // Calculate days_until_deletion before moving item fields
        let days_until_deletion = item.days_until_deletion();

        // Determine display fields based on item type
        let (category, icon_class, icon_special_class) = match item.item_type() {
            TrashedItemType::Folder => (
                "Folder".to_string(),
                "fas fa-folder".to_string(),
                "folder-icon".to_string(),
            ),
            TrashedItemType::File => {
                let name = item.name();
                // Use empty MIME type to leverage extension fallback
                let category = category_for(name, "").to_string();
                let icon_class = icon_class_for(name, "").to_string();
                let icon_special_class = icon_special_class_for(name, "").to_string();
                (category, icon_class, icon_special_class)
            }
        };

        TrashedItemDto {
            id: item.id().to_string(),
            original_id: item.original_id().to_string(),
            item_type: match item.item_type() {
                TrashedItemType::File => "file".to_string(),
                TrashedItemType::Folder => "folder".to_string(),
            },
            name: item.name().to_string(),
            original_path: item.original_path().to_string(),
            trashed_at: item.trashed_at(),
            days_until_deletion,
            category,
            icon_class,
            icon_special_class,
        }
    }

    /// Validates that the given user owns the trashed item.
    /// Returns an error if the item does not exist or belongs to a different user.
    #[instrument(skip(self))]
    async fn _validate_user_ownership(&self, item_id: &str, user_id: &str) -> Result<()> {
        let item_uuid = Uuid::parse_str(item_id)
            .map_err(|e| DomainError::validation_error(format!("Invalid item ID: {}", e)))?;
        let user_uuid = Uuid::parse_str(user_id)
            .map_err(|e| DomainError::validation_error(format!("Invalid user ID: {}", e)))?;

        match self
            .trash_repository
            .get_trash_item(&item_uuid, &user_uuid)
            .await?
        {
            Some(item) => {
                if item.user_id() != user_uuid {
                    error!(
                        "User {} attempted to access trash item {} owned by {}",
                        user_id,
                        item_id,
                        item.user_id()
                    );
                    return Err(DomainError::access_denied(
                        "TrashItem",
                        "You do not have permission to access this trash item",
                    ));
                }
                Ok(())
            }
            None => {
                // Item not found for this user — treat as authorization error
                // to avoid leaking existence information
                Err(DomainError::not_found(
                    "TrashItem",
                    format!("{} (user: {})", item_id, user_id),
                ))
            }
        }
    }
}

impl TrashUseCase for TrashService {
    #[instrument(skip(self))]
    async fn get_trash_items(&self, user_id: Uuid) -> Result<Vec<TrashedItemDto>> {
        debug!("Getting trash items for user: {}", user_id);

        let items = self.trash_repository.get_trash_items(&user_id).await?;

        let dtos = items.into_iter().map(|item| self.to_dto(item)).collect();

        Ok(dtos)
    }

    #[instrument(skip(self))]
    async fn move_to_trash(&self, item_id: &str, item_type: &str, user_id: Uuid) -> Result<()> {
        info!(
            "Moving to trash: type={}, id={}, user={}",
            item_type, item_id, user_id
        );
        debug!("User UUID validation: {}", user_id);

        // Note: We now verify file/folder ownership BEFORE moving to trash.
        // This prevents users from trashing items they do not own (IDOR).

        // Parse UUIDs with detailed error handling
        debug!("Validating item UUID: {}", item_id);
        let item_uuid = match Uuid::parse_str(item_id) {
            Ok(uuid) => {
                debug!("Valid item UUID: {}", uuid);
                uuid
            }
            Err(e) => {
                error!("Invalid item UUID: {} - Error: {}", item_id, e);
                return Err(DomainError::validation_error(format!(
                    "Invalid item ID: {}",
                    e
                )));
            }
        };

        let user_uuid = user_id;

        match item_type {
            "file" => {
                info!("Processing file to move to trash: {}", item_id);

                // Get the file — ownership-verified at SQL level.
                // Returns NotFound if the file does not exist OR belongs to
                // another user, preventing cross-user trash operations.
                debug!("Getting file data (owner-scoped): {}", item_id);
                let file = match self
                    .file_read_port
                    .get_file_for_owner(item_id, user_id)
                    .await
                {
                    Ok(file) => {
                        debug!("File found: {} ({})", file.name(), item_id);
                        file
                    }
                    Err(e) => {
                        error!("Error getting file: {} - {}", item_id, e);
                        return Err(DomainError::new(
                            ErrorKind::NotFound,
                            "File",
                            format!("Error retrieving file {}: {}", item_id, e),
                        ));
                    }
                };

                let original_path = file.storage_path().to_string();
                debug!("Original file path: {}", original_path);

                // Create the trash item
                debug!("Creating TrashedItem object for the file");
                let trashed_item = TrashedItem::new(
                    item_uuid,
                    user_uuid,
                    TrashedItemType::File,
                    file.name().to_string(),
                    original_path,
                    self.retention_days,
                );
                debug!(
                    "TrashedItem created successfully: {} -> {}",
                    file.name(),
                    trashed_item.id()
                );

                // First add to trash index to register the item
                info!("Adding file {} to trash index", item_id);
                match self.trash_repository.add_to_trash(&trashed_item).await {
                    Ok(_) => {
                        debug!("File added to trash index successfully");
                    }
                    Err(e) => {
                        error!("Error adding file to trash index: {}", e);
                        return Err(DomainError::internal_error(
                            "TrashRepository",
                            format!("Failed to add file to trash: {}", e),
                        ));
                    }
                };

                // Then physically move the file to trash
                info!("Physically moving file to trash: {}", item_id);
                match self.file_write_port.move_to_trash(item_id).await {
                    Ok(_) => {
                        debug!("File physically moved to trash successfully: {}", item_id);
                    }
                    Err(e) => {
                        error!("Error physically moving file to trash: {} - {}", item_id, e);
                        return Err(DomainError::new(
                            ErrorKind::InternalError,
                            "File",
                            format!("Error moving file {} to trash: {}", item_id, e),
                        ));
                    }
                }

                info!("File completely moved to trash: {}", item_id);
                Ok(())
            }
            "folder" => {
                // Get the folder and verify ownership.
                // Returns NotFound if the folder does not exist or belongs
                // to another user — prevents cross-user trash operations.
                let folder = self
                    .folder_storage_port
                    .get_folder(item_id)
                    .await
                    .map_err(|e| {
                        DomainError::new(
                            ErrorKind::NotFound,
                            "Folder",
                            format!("Error retrieving folder {}: {}", item_id, e),
                        )
                    })?;

                // Ownership check — return NotFound (not Forbidden) to
                // prevent leaking whether the folder exists.
                if folder.owner_id() != Some(user_id) {
                    return Err(DomainError::not_found(
                        "Folder",
                        format!("Folder not found: {}", item_id),
                    ));
                }

                let original_path = folder.storage_path().to_string();

                // Create the trash item
                let trashed_item = TrashedItem::new(
                    item_uuid,
                    user_uuid,
                    TrashedItemType::Folder,
                    folder.name().to_string(),
                    original_path,
                    self.retention_days,
                );

                // First add to trash index to register the item
                debug!("Adding folder {} to trash repository", item_id);
                match self.trash_repository.add_to_trash(&trashed_item).await {
                    Ok(_) => debug!("Successfully added folder to trash repository"),
                    Err(e) => {
                        error!("Failed to add folder to trash repository: {}", e);
                        return Err(DomainError::internal_error(
                            "TrashRepository",
                            format!("Failed to add folder to trash: {}", e),
                        ));
                    }
                };

                // Then physically move the folder to trash
                self.folder_storage_port
                    .move_to_trash(item_id)
                    .await
                    .map_err(|e| {
                        DomainError::new(
                            ErrorKind::InternalError,
                            "Folder",
                            format!("Error moving folder {} to trash: {}", item_id, e),
                        )
                    })?;

                debug!("Folder moved to trash: {}", item_id);
                Ok(())
            }
            _ => Err(DomainError::validation_error(format!(
                "Invalid item type: {}",
                item_type
            ))),
        }
    }

    #[instrument(skip(self))]
    async fn restore_item(&self, trash_id: &str, user_id: Uuid) -> Result<()> {
        info!("Restoring item {} for user {}", trash_id, user_id);

        let trash_uuid = match Uuid::parse_str(trash_id) {
            Ok(id) => {
                info!("Trash UUID parsed successfully: {}", id);
                id
            }
            Err(e) => {
                error!("Invalid trash ID format: {} - {}", trash_id, e);
                return Err(DomainError::validation_error(format!(
                    "Invalid trash ID: {}",
                    e
                )));
            }
        };

        let user_uuid = user_id;

        // Get the trash item
        info!("Retrieving trash item from repository: ID={}", trash_id);
        let item_result = self
            .trash_repository
            .get_trash_item(&trash_uuid, &user_uuid)
            .await;

        match item_result {
            Ok(Some(item)) => {
                info!(
                    "Found item in trash: ID={}, Type={:?}, OriginalID={}",
                    trash_id,
                    item.item_type(),
                    item.original_id()
                );

                // Restore based on type
                match item.item_type() {
                    TrashedItemType::File => {
                        // Restore the file to its original location
                        let file_id = item.original_id().to_string();
                        let original_path = item.original_path().to_string();

                        info!(
                            "Restoring file from trash: ID={}, OriginalPath={}",
                            file_id, original_path
                        );
                        match self
                            .file_write_port
                            .restore_from_trash(&file_id, &original_path)
                            .await
                        {
                            Ok(_) => {
                                info!("Successfully restored file from trash: {}", file_id);
                            }
                            Err(e) => {
                                // Check if the error is because the file is not found
                                if format!("{}", e).contains("not found") {
                                    info!(
                                        "File not found in trash, may already have been restored: {}",
                                        file_id
                                    );
                                    // We continue so we can clean up the trash entry
                                } else {
                                    // Return error for other kinds of errors
                                    error!("Error restoring file from trash: {} - {}", file_id, e);
                                    return Err(DomainError::new(
                                        ErrorKind::InternalError,
                                        "File",
                                        format!(
                                            "Error restoring file {} from trash: {}",
                                            file_id, e
                                        ),
                                    ));
                                }
                            }
                        }
                    }
                    TrashedItemType::Folder => {
                        // Restore the folder to its original location
                        let folder_id = item.original_id().to_string();
                        let original_path = item.original_path().to_string();

                        info!(
                            "Restoring folder from trash: ID={}, OriginalPath={}",
                            folder_id, original_path
                        );
                        match self
                            .folder_storage_port
                            .restore_from_trash(&folder_id, &original_path)
                            .await
                        {
                            Ok(_) => {
                                info!("Successfully restored folder from trash: {}", folder_id);
                            }
                            Err(e) => {
                                // Check if the error is because the folder is not found
                                if format!("{}", e).contains("not found") {
                                    info!(
                                        "Folder not found in trash, may already have been restored: {}",
                                        folder_id
                                    );
                                    // We continue so we can clean up the trash entry
                                } else {
                                    // Return error for other kinds of errors
                                    error!(
                                        "Error restoring folder from trash: {} - {}",
                                        folder_id, e
                                    );
                                    return Err(DomainError::new(
                                        ErrorKind::InternalError,
                                        "Folder",
                                        format!(
                                            "Error restoring folder {} from trash: {}",
                                            folder_id, e
                                        ),
                                    ));
                                }
                            }
                        }
                    }
                }

                // Always remove the item from the trash index to maintain consistency
                info!(
                    "Removing item from trash index after restoration: {}",
                    trash_id
                );
                match self
                    .trash_repository
                    .restore_from_trash(&trash_uuid, &user_uuid)
                    .await
                {
                    Ok(_) => {
                        info!("Successfully removed entry from trash index: {}", trash_id);
                    }
                    Err(e) => {
                        error!(
                            "Error removing entry from trash index: {} - {}",
                            trash_id, e
                        );
                        return Err(DomainError::new(
                            ErrorKind::InternalError,
                            "Trash",
                            format!("Error removing trash entry after restoration: {}", e),
                        ));
                    }
                }

                info!("Item successfully restored from trash: {}", trash_id);
                Ok(())
            }
            Ok(None) => {
                // If the item isn't found in trash, we can just return success
                info!(
                    "Item not found in trash index, considering as already restored: {}",
                    trash_id
                );
                Ok(())
            }
            Err(e) => {
                // Something went wrong with the repository
                error!(
                    "Error retrieving item from trash repository: {} - {}",
                    trash_id, e
                );
                Err(e)
            }
        }
    }

    #[instrument(skip(self))]
    async fn delete_permanently(&self, trash_id: &str, user_id: Uuid) -> Result<()> {
        info!(
            "Permanently deleting item {} for user {}",
            trash_id, user_id
        );

        let trash_uuid = match Uuid::parse_str(trash_id) {
            Ok(id) => {
                info!("Trash UUID parsed successfully: {}", id);
                id
            }
            Err(e) => {
                error!("Invalid trash ID format: {} - {}", trash_id, e);
                return Err(DomainError::validation_error(format!(
                    "Invalid trash ID: {}",
                    e
                )));
            }
        };

        let user_uuid = user_id;

        // Get the trash item
        info!("Retrieving trash item from repository: ID={}", trash_id);
        let item_result = self
            .trash_repository
            .get_trash_item(&trash_uuid, &user_uuid)
            .await;

        match item_result {
            Ok(Some(item)) => {
                info!(
                    "Found item in trash: ID={}, Type={:?}, OriginalID={}",
                    trash_id,
                    item.item_type(),
                    item.original_id()
                );

                // Permanently delete based on type
                match item.item_type() {
                    TrashedItemType::File => {
                        // Permanently delete the file
                        let file_id = item.original_id().to_string();

                        info!("Permanently deleting file: {}", file_id);
                        match self.file_write_port.delete_file_permanently(&file_id).await {
                            Ok(_) => {
                                info!("Successfully deleted file permanently: {}", file_id);
                                // Invalidate content cache for the deleted file.
                                if let Some(cc) = &self.content_cache {
                                    cc.invalidate(&file_id).await;
                                }
                            }
                            Err(e) => {
                                // Check if the file is not found - in that case, we can continue
                                // because we still want to remove the item from the trash index
                                if format!("{}", e).contains("not found") {
                                    info!(
                                        "File not found, may already have been deleted: {}",
                                        file_id
                                    );
                                } else {
                                    // Return error for other types of errors
                                    error!("Error permanently deleting file: {} - {}", file_id, e);
                                    return Err(DomainError::new(
                                        ErrorKind::InternalError,
                                        "File",
                                        format!(
                                            "Error deleting file {} permanently: {}",
                                            file_id, e
                                        ),
                                    ));
                                }
                            }
                        }

                        // Best-effort thumbnail cleanup — thumbnails are cache
                        // artifacts, so failure must not block file deletion.
                        if let Some(thumb) = &self.thumbnail_service
                            && let Err(e) = thumb.delete_thumbnails(&file_id).await
                        {
                            warn!("Failed to delete thumbnails for file {}: {}", file_id, e);
                        }
                    }
                    TrashedItemType::Folder => {
                        // Permanently delete the folder
                        let folder_id = item.original_id().to_string();

                        info!("Permanently deleting folder: {}", folder_id);
                        match self
                            .folder_storage_port
                            .delete_folder_permanently(&folder_id)
                            .await
                        {
                            Ok(_) => {
                                info!("Successfully deleted folder permanently: {}", folder_id);
                            }
                            Err(e) => {
                                // Check if the folder is not found - in that case, we can continue
                                if format!("{}", e).contains("not found") {
                                    info!(
                                        "Folder not found, may already have been deleted: {}",
                                        folder_id
                                    );
                                } else {
                                    // Return error for other types of errors
                                    error!(
                                        "Error permanently deleting folder: {} - {}",
                                        folder_id, e
                                    );
                                    return Err(DomainError::new(
                                        ErrorKind::InternalError,
                                        "Folder",
                                        format!(
                                            "Error deleting folder {} permanently: {}",
                                            folder_id, e
                                        ),
                                    ));
                                }
                            }
                        }
                    }
                }

                // Always remove the item from trash index to maintain consistency
                info!("Removing entry from trash index: {}", trash_id);
                match self
                    .trash_repository
                    .delete_permanently(&trash_uuid, &user_uuid)
                    .await
                {
                    Ok(_) => {
                        info!("Successfully removed entry from trash index: {}", trash_id);
                    }
                    Err(e) => {
                        error!(
                            "Error removing entry from trash index: {} - {}",
                            trash_id, e
                        );
                        return Err(DomainError::new(
                            ErrorKind::InternalError,
                            "Trash",
                            format!("Error removing trash entry: {}", e),
                        ));
                    }
                };

                info!("Item permanently deleted from trash: {}", trash_id);
                Ok(())
            }
            Ok(None) => {
                // If the item isn't found in trash, we can just return success
                info!(
                    "Item not found in trash, considering as already deleted: {}",
                    trash_id
                );
                Ok(())
            }
            Err(e) => {
                // Something went wrong with the repository
                error!(
                    "Error retrieving item from trash repository: {} - {}",
                    trash_id, e
                );
                Err(e)
            }
        }
    }

    #[instrument(skip(self))]
    async fn empty_trash(&self, user_id: Uuid) -> Result<()> {
        info!("Emptying trash for user {}", user_id);

        // Collect trashed file IDs BEFORE bulk-deleting so we can clean up
        // their thumbnails afterward.  This is best-effort — if the query
        // fails we still proceed with the bulk delete.
        let trashed_file_ids: Vec<String> = if self.thumbnail_service.is_some() {
            match self.trash_repository.get_trash_items(&user_id).await {
                Ok(items) => items
                    .iter()
                    .filter(|i| matches!(i.item_type(), TrashedItemType::File))
                    .map(|i| i.original_id().to_string())
                    .collect(),
                Err(e) => {
                    warn!("Could not list trashed items for thumbnail cleanup: {}", e);
                    Vec::new()
                }
            }
        } else {
            Vec::new()
        };

        // clear_trash() already performs bulk SQL DELETEs in 2 queries:
        //   1. DELETE FROM storage.files  WHERE user_id = $1 AND is_trashed = TRUE
        //   2. DELETE FROM storage.folders WHERE user_id = $1 AND is_trashed = TRUE
        //
        // Folder deletion cascades (FK ON DELETE CASCADE) to child folders and
        // their files. The PG trigger `trg_files_decrement_blob_ref` automatically
        // decrements blob ref_counts for every deleted file row — no Rust-side
        // remove_reference() call is needed.
        //
        // Finally it clears the trash_items index for the user.
        self.trash_repository.clear_trash(&user_id).await?;

        // Invalidate content cache for all permanently deleted files.
        if let Some(cc) = &self.content_cache {
            for file_id in &trashed_file_ids {
                cc.invalidate(file_id).await;
            }
        }

        // Best-effort thumbnail cleanup for all deleted files
        if let Some(thumb) = &self.thumbnail_service {
            for file_id in &trashed_file_ids {
                if let Err(e) = thumb.delete_thumbnails(file_id).await {
                    warn!("Failed to delete thumbnails for file {}: {}", file_id, e);
                }
            }
        }

        info!("Trash emptied for user {}", user_id);
        Ok(())
    }
}
