use crate::controllers::HttpResult;
use crate::controllers::server_controller::ServerConstraints;
use crate::extractors::auth_extractor::AuthExtractor;
use crate::extractors::file_extractor::FileExtractor;
use crate::extractors::file_id_extractor::FileIdExtractor;
use crate::models::files::File;
use crate::services::files_service;
use crate::utils::time_utils;
use actix_web::http::header;
use actix_web::{HttpRequest, HttpResponse, web};
use std::ops::Add;
use std::time::Duration;

/// Upload a new file
pub async fn upload(auth: AuthExtractor, file: FileExtractor) -> HttpResult {
    // Check file mimetype
    if !ServerConstraints::default()
        .file_allowed_types
        .contains(&file.mime.as_ref())
    {
        log::error!(
            "User attempted to upload a file with invalid mimetype! {}",
            file.mime
        );
        return Ok(HttpResponse::BadRequest().body(format!(
            "Files with mimetype {} cannot be uploaded!",
            file.mime
        )));
    }

    let file = files_service::create_file_with_mimetype(
        auth.user_id(),
        &file.name(),
        &file.mime,
        &file.buff,
    )
    .await?;

    Ok(HttpResponse::Ok().json(file))
}

/// Get information about a file
pub async fn get_info(file_extractor: FileIdExtractor) -> HttpResult {
    Ok(HttpResponse::Ok().json(file_extractor.as_ref()))
}

#[derive(serde::Deserialize)]
pub struct DownloadQuery {
    #[serde(default)]
    download: bool,
}

/// Download an uploaded file
pub async fn download(
    req: HttpRequest,
    file_extractor: FileIdExtractor,
    query: web::Query<DownloadQuery>,
) -> HttpResult {
    serve_file(req, file_extractor.as_ref(), query.download).await
}

/// Serve a file, returning 304 status code if the requested file already exists
pub async fn serve_file(req: HttpRequest, file: &File, download_file: bool) -> HttpResult {
    if !download_file {
        // Check if the browser already knows the etag
        if let Some(c) = req.headers().get(header::IF_NONE_MATCH)
            && c.to_str().unwrap_or("") == file.sha512.as_str()
        {
            return Ok(HttpResponse::NotModified().finish());
        }

        // Check if the browser already knows the file by date
        if let Some(c) = req.headers().get(header::IF_MODIFIED_SINCE) {
            let date_str = c.to_str().unwrap_or("");
            if let Ok(date) = httpdate::parse_http_date(date_str)
                && date.add(Duration::from_secs(1))
                    >= time_utils::unix_to_system_time(file.time_create as u64)
            {
                return Ok(HttpResponse::NotModified().finish());
            }
        }
    }
    let mut res = HttpResponse::Ok();
    res.content_type(file.mime_type.as_str())
        .insert_header(("etag", file.sha512.as_str()))
        .insert_header((
            "last-modified",
            time_utils::unix_to_http_date(file.time_create as u64),
        ));

    // Add filename to response headers if requested
    if download_file {
        res.insert_header((
            "content-disposition",
            format!("attachment; filename={}", file.file_name),
        ));
    }

    Ok(res.body(files_service::get_file_content(file).await?))
}

/// Delete an uploaded file
pub async fn delete(file_extractor: FileIdExtractor) -> HttpResult {
    match files_service::delete_file_if_unused(file_extractor.as_ref().id(), true).await {
        Ok(true) => Ok(HttpResponse::Accepted().finish()),
        Ok(false) => Ok(HttpResponse::Conflict().finish()),
        Err(e) => {
            log::error!("Failed to delete file: {e}");
            Ok(HttpResponse::InternalServerError().finish())
        }
    }
}
