use axum::{
    Json,
    extract::{Path, Query, State},
    http::StatusCode,
    response::IntoResponse,
};
use serde::Deserialize;
use std::sync::Arc;
use tracing::{error, info};

use crate::application::dtos::display_helpers::{
    category_for, format_file_size, icon_class_for, icon_special_class_for,
};
use crate::application::dtos::file_dto::FileDto;
use crate::application::dtos::folder_dto::FolderDto;
use crate::application::dtos::grant_dto::{ResourceContentDto, ResourceTypeDto};
use crate::application::dtos::recent_dto::{
    RecentResourceItemDto, RecentResourcesDto, RecentResourcesQuery,
};
use crate::application::ports::recent_ports::RecentItemsUseCase;
use crate::application::services::recent_service::RecentService;
use crate::interfaces::errors::AppError;
use crate::interfaces::middleware::auth::AuthUser;

/// Query parameters for getting recent items
#[derive(Deserialize)]
pub struct GetRecentParams {
    #[serde(default)]
    limit: Option<i32>,
}

/// Get user's recent items (deprecated — use `GET /api/recent/resources` instead)
#[deprecated = "Use GET /api/recent/resources instead"]
#[utoipa::path(
    get,
    path = "/api/recent",
    responses(
        (status = 200, description = "List of recent items", body = Vec<crate::application::dtos::recent_dto::RecentItemDto>)
    ),
    security(("bearerAuth" = [])),
    tag = "recent"
)]
pub async fn get_recent_items(
    State(recent_service): State<Arc<RecentService>>,
    auth_user: AuthUser,
    Query(params): Query<GetRecentParams>,
) -> impl IntoResponse {
    let user_id = auth_user.id;

    match recent_service.get_recent_items(user_id, params.limit).await {
        Ok(items) => {
            info!("Retrieved {} recent items for user", items.len());
            (StatusCode::OK, Json(items)).into_response()
        }
        Err(err) => {
            error!("Error retrieving recent items: {}", err);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({
                    "error": "Failed to retrieve recent items"
                })),
            )
                .into_response()
        }
    }
}

/// Record access to an item
#[utoipa::path(
    post,
    path = "/api/recent/{item_type}/{item_id}",
    params(
        ("item_type" = String, Path, description = "Item type (file or folder)"),
        ("item_id" = String, Path, description = "Item ID")
    ),
    responses(
        (status = 200, description = "Access recorded"),
        (status = 400, description = "Invalid item type")
    ),
    security(("bearerAuth" = [])),
    tag = "recent"
)]
pub async fn record_item_access(
    State(recent_service): State<Arc<RecentService>>,
    auth_user: AuthUser,
    Path((item_type, item_id)): Path<(String, String)>,
) -> impl IntoResponse {
    let user_id = auth_user.id;

    // Validate item type
    if item_type != "file" && item_type != "folder" {
        return (
            StatusCode::BAD_REQUEST,
            Json(serde_json::json!({
                "error": "Item type must be 'file' or 'folder'"
            })),
        )
            .into_response();
    }

    match recent_service
        .record_item_access(user_id, &item_id, &item_type)
        .await
    {
        Ok(_) => {
            info!("Recorded access to {} '{}' in recents", item_type, item_id);
            (
                StatusCode::OK,
                Json(serde_json::json!({
                    "message": "Access recorded successfully"
                })),
            )
                .into_response()
        }
        Err(err) => {
            error!("Error recording access in recents: {}", err);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({
                    "error": "Failed to record access"
                })),
            )
                .into_response()
        }
    }
}

/// Remove an item from recents
#[utoipa::path(
    delete,
    path = "/api/recent/{item_type}/{item_id}",
    params(
        ("item_type" = String, Path, description = "Item type (file or folder)"),
        ("item_id" = String, Path, description = "Item ID")
    ),
    responses(
        (status = 200, description = "Item removed from recents"),
        (status = 404, description = "Item not in recents")
    ),
    security(("bearerAuth" = [])),
    tag = "recent"
)]
pub async fn remove_from_recent(
    State(recent_service): State<Arc<RecentService>>,
    auth_user: AuthUser,
    Path((item_type, item_id)): Path<(String, String)>,
) -> impl IntoResponse {
    let user_id = auth_user.id;

    match recent_service
        .remove_from_recent(user_id, &item_id, &item_type)
        .await
    {
        Ok(removed) => {
            if removed {
                info!("Removed {} '{}' from recents", item_type, item_id);
                (
                    StatusCode::OK,
                    Json(serde_json::json!({
                        "message": "Item removed from recents"
                    })),
                )
                    .into_response()
            } else {
                info!("Item {} '{}' was not in recents", item_type, item_id);
                (
                    StatusCode::NOT_FOUND,
                    Json(serde_json::json!({
                        "message": "Item was not in recents"
                    })),
                )
                    .into_response()
            }
        }
        Err(err) => {
            error!("Error removing from recents: {}", err);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({
                    "error": "Failed to remove from recents"
                })),
            )
                .into_response()
        }
    }
}

/// Clear all recent items
#[utoipa::path(
    delete,
    path = "/api/recent/clear",
    responses(
        (status = 200, description = "Recent items cleared")
    ),
    security(("bearerAuth" = [])),
    tag = "recent"
)]
pub async fn clear_recent_items(
    State(recent_service): State<Arc<RecentService>>,
    auth_user: AuthUser,
) -> impl IntoResponse {
    let user_id = auth_user.id;

    match recent_service.clear_recent_items(user_id).await {
        Ok(_) => {
            info!("Cleared all recent items for user");
            (
                StatusCode::OK,
                Json(serde_json::json!({
                    "message": "Recent items cleared successfully"
                })),
            )
                .into_response()
        }
        Err(err) => {
            error!("Error clearing recent items: {}", err);
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                Json(serde_json::json!({
                    "error": "Failed to clear recent items"
                })),
            )
                .into_response()
        }
    }
}

/// List recently accessed resources with cursor pagination.
///
/// Sorted by `accessed_at` DESC by default (most recently accessed first).
/// `path` is cleared when the resource is not owned by the requesting user.
#[utoipa::path(
    get,
    path = "/api/recent/resources",
    params(RecentResourcesQuery),
    responses(
        (status = 200, description = "Paginated list of recently accessed resources",
         body = RecentResourcesDto),
        (status = 400, description = "Invalid cursor or query parameters"),
    ),
    security(("bearerAuth" = [])),
    tag = "recent"
)]
pub async fn list_recent_resources(
    State(recent_service): State<Arc<RecentService>>,
    auth_user: AuthUser,
    Query(q): Query<RecentResourcesQuery>,
) -> impl IntoResponse {
    let user_id = auth_user.id;

    let order_by = q.order_by.as_deref().unwrap_or("accessed_at").to_owned();

    // If a cursor exists, validate that it matches the requested sort/direction.
    let cursor = q
        .decode_cursor()
        .filter(|c| c.order_by == order_by && c.reverse == q.reverse);

    let kinds = q.resource_kinds();

    match recent_service
        .list_resources_paged(
            user_id,
            q.limit_clamped(),
            cursor,
            &order_by,
            kinds.as_deref(),
            q.reverse,
        )
        .await
    {
        Ok((rows, next_cursor)) => {
            let items: Vec<RecentResourceItemDto> = rows
                .into_iter()
                .map(|row| {
                    // Path is only shown to the owner; non-owners see ""
                    // to avoid leaking another user's folder hierarchy.
                    let path = if row.is_owner {
                        row.path.clone().unwrap_or_default()
                    } else {
                        String::new()
                    };

                    if row.resource_type == "folder" {
                        let dto = FolderDto {
                            id: row.resource_id.to_string(),
                            name: row.name.clone(),
                            path,
                            parent_id: row.parent_id.map(|u| u.to_string()),
                            owner_id: Some(row.owner_id.to_string()),
                            created_at: row.resource_created_at.timestamp() as u64,
                            modified_at: row.modified_at.timestamp() as u64,
                            is_root: false,
                            icon_class: std::sync::Arc::from("fas fa-folder"),
                            icon_special_class: std::sync::Arc::from("folder-icon"),
                            category: std::sync::Arc::from("Folder"),
                        };
                        RecentResourceItemDto {
                            resource_type: ResourceTypeDto::Folder,
                            accessed_at: row.accessed_at,
                            resource: ResourceContentDto::Folder(dto),
                        }
                    } else {
                        let mime = row
                            .mime_type
                            .as_deref()
                            .unwrap_or("application/octet-stream");
                        let size_bytes = row.size.max(0) as u64;
                        let dto = FileDto {
                            id: row.resource_id.to_string(),
                            name: row.name.clone(),
                            path,
                            size: size_bytes,
                            mime_type: std::sync::Arc::from(mime),
                            folder_id: row.parent_id.map(|u| u.to_string()),
                            created_at: row.resource_created_at.timestamp() as u64,
                            modified_at: row.modified_at.timestamp() as u64,
                            icon_class: std::sync::Arc::from(icon_class_for(&row.name, mime)),
                            icon_special_class: std::sync::Arc::from(icon_special_class_for(
                                &row.name, mime,
                            )),
                            category: std::sync::Arc::from(category_for(&row.name, mime)),
                            size_formatted: format_file_size(size_bytes),
                            owner_id: Some(row.owner_id.to_string()),
                            sort_date: None,
                            etag: String::new(),
                        };
                        RecentResourceItemDto {
                            resource_type: ResourceTypeDto::File,
                            accessed_at: row.accessed_at,
                            resource: ResourceContentDto::File(dto),
                        }
                    }
                })
                .collect();

            (
                StatusCode::OK,
                Json(RecentResourcesDto::with_cursor(items, next_cursor)),
            )
                .into_response()
        }
        Err(e) => AppError::from(e).into_response(),
    }
}
