use std::collections::HashSet;
use std::sync::Arc;

use tracing::info;
use uuid::Uuid;

use crate::application::dtos::cursor::PageCursor;
use crate::application::dtos::favorites_dto::{
    BatchFavoritesResult, BatchFavoritesStats, FavoriteItemDto, FavoriteResourceRow,
    FavoritesCursor,
};
use crate::application::ports::favorites_ports::{FavoritesRepositoryPort, FavoritesUseCase};
use crate::common::errors::{DomainError, ErrorKind, Result};
use crate::domain::services::authorization::ResourceKind;
use crate::infrastructure::repositories::pg::FavoritesPgRepository;

/// Implementation of the FavoritesUseCase for managing user favorites.
///
/// Depends on `FavoritesRepositoryPort` (outbound port) instead of
/// accessing the database directly, following hexagonal architecture.
pub struct FavoritesService {
    repo: Arc<FavoritesPgRepository>,
}

impl FavoritesService {
    /// Create a new FavoritesService with the given repository port
    pub fn new(repo: Arc<FavoritesPgRepository>) -> Self {
        Self { repo }
    }
}

impl FavoritesUseCase for FavoritesService {
    /// Get all favorites for a user
    async fn get_favorites(&self, user_id: Uuid) -> Result<Vec<FavoriteItemDto>> {
        info!("Getting favorites for user: {}", user_id);
        let favorites = self.repo.get_favorites(user_id).await?;
        info!(
            "Retrieved {} favorites for user {}",
            favorites.len(),
            user_id
        );
        Ok(favorites)
    }

    /// Add an item to user's favorites
    async fn add_to_favorites(&self, user_id: Uuid, item_id: &str, item_type: &str) -> Result<()> {
        info!(
            "Adding {} '{}' to favorites for user {}",
            item_type, item_id, user_id
        );

        if item_type != "file" && item_type != "folder" {
            return Err(DomainError::new(
                ErrorKind::InvalidInput,
                "Favorites",
                "Item type must be 'file' or 'folder'",
            ));
        }

        self.repo.add_favorite(user_id, item_id, item_type).await?;
        info!(
            "Successfully added {} '{}' to favorites for user {}",
            item_type, item_id, user_id
        );
        Ok(())
    }

    /// Remove an item from user's favorites
    async fn remove_from_favorites(
        &self,
        user_id: Uuid,
        item_id: &str,
        item_type: &str,
    ) -> Result<bool> {
        info!(
            "Removing {} '{}' from favorites for user {}",
            item_type, item_id, user_id
        );
        let removed = self
            .repo
            .remove_favorite(user_id, item_id, item_type)
            .await?;
        info!(
            "{} {} '{}' from favorites for user {}",
            if removed {
                "Successfully removed"
            } else {
                "Did not find"
            },
            item_type,
            item_id,
            user_id
        );
        Ok(removed)
    }

    /// Check if an item is in user's favorites
    async fn is_favorite(&self, user_id: Uuid, item_id: &str, item_type: &str) -> Result<bool> {
        info!(
            "Checking if {} '{}' is favorite for user {}",
            item_type, item_id, user_id
        );
        self.repo.is_favorite(user_id, item_id, item_type).await
    }

    async fn batch_add_to_favorites(
        &self,
        user_id: Uuid,
        items: &[(String, String)],
    ) -> Result<BatchFavoritesResult> {
        info!(
            "Batch adding {} items to favorites for user {}",
            items.len(),
            user_id
        );

        // Validate all item types
        for (item_id, item_type) in items {
            if item_type != "file" && item_type != "folder" {
                return Err(DomainError::new(
                    ErrorKind::InvalidInput,
                    "Favorites",
                    format!(
                        "Item type must be 'file' or 'folder' for item '{}'",
                        item_id
                    ),
                ));
            }
        }

        let requested = items.len();
        let inserted = self.repo.add_favorites_batch(user_id, items).await?;
        let already_existed = requested as u64 - inserted;

        info!(
            "Batch favorites for user {}: {} requested, {} inserted, {} already existed",
            user_id, requested, inserted, already_existed
        );

        // Return the full enriched list so the client can replace its cache
        let favorites = self.repo.get_favorites(user_id).await?;

        Ok(BatchFavoritesResult {
            stats: BatchFavoritesStats {
                requested,
                inserted,
                already_existed,
            },
            favorites,
        })
    }

    async fn batch_check_favorites(
        &self,
        user_id: Uuid,
        item_ids: &[(&str, &str)],
    ) -> Result<HashSet<String>> {
        self.repo.batch_check_favorites(user_id, item_ids).await
    }
}

impl FavoritesService {
    /// Cursor-paginated list of the user's favorited resources.
    ///
    /// No authz needed — favorites are strictly user-scoped; the repository
    /// enforces `WHERE user_id = $1` so users can only see their own entries.
    ///
    /// Returns `(rows, next_cursor_encoded)`.
    pub async fn list_resources_paged(
        &self,
        user_id: Uuid,
        limit: usize,
        cursor: Option<FavoritesCursor>,
        order_by: &str,
        kinds: Option<&[ResourceKind]>,
        reverse: bool,
    ) -> Result<(Vec<FavoriteResourceRow>, Option<String>)> {
        // Fetch one extra row to detect whether a next page exists.
        let mut rows = self
            .repo
            .list_resources_paged(
                user_id,
                limit + 1,
                cursor.as_ref(),
                order_by,
                kinds,
                reverse,
            )
            .await?;

        let next_cursor = if rows.len() > limit {
            let last = &rows[limit - 1];
            let c = build_favorites_cursor(last, order_by, reverse);
            rows.truncate(limit);
            Some(c.encode())
        } else {
            None
        };

        Ok((rows, next_cursor))
    }
}

/// Build the next-page cursor from the last row of the current page.
/// `reverse` is stored in the cursor so subsequent pages use the same direction.
fn build_favorites_cursor(
    row: &FavoriteResourceRow,
    order_by: &str,
    reverse: bool,
) -> FavoritesCursor {
    match order_by {
        "type" => FavoritesCursor {
            order_by: "type".to_owned(),
            resource_id: row.resource_id,
            sort_str: row.sort_str.clone(), // LOWER(name)
            sort_int: row.sort_int,         // type_order
            sort_ts: None,
            reverse,
        },
        "favorited_at" => FavoritesCursor {
            order_by: "favorited_at".to_owned(),
            resource_id: row.resource_id,
            sort_str: None,
            sort_int: None,
            sort_ts: row.sort_ts, // favorited_at timestamp
            reverse,
        },
        "modified_at" => FavoritesCursor {
            order_by: "modified_at".to_owned(),
            resource_id: row.resource_id,
            sort_str: None,
            sort_int: None,
            sort_ts: row.sort_ts, // modified_at timestamp
            reverse,
        },
        "size" => FavoritesCursor {
            order_by: "size".to_owned(),
            resource_id: row.resource_id,
            sort_str: None,
            sort_int: row.sort_int, // file size in bytes
            sort_ts: None,
            reverse,
        },
        "owner" => FavoritesCursor {
            order_by: "owner".to_owned(),
            resource_id: row.resource_id,
            sort_str: row.sort_str.clone(), // LOWER(username)
            sort_int: None,
            sort_ts: row.sort_ts, // favorited_at (secondary sort)
            reverse,
        },
        _ => FavoritesCursor {
            // "name" (default): sort_str = LOWER(name), sort_int = folder_first (0 = folder, 1 = file)
            order_by: "name".to_owned(),
            resource_id: row.resource_id,
            sort_str: row.sort_str.clone(),
            sort_int: row.sort_int, // folder_first
            sort_ts: None,
            reverse,
        },
    }
}
