use crate::app_config::AppConfig;
use crate::constants;
use crate::controllers::HttpResult;
use crate::utils::files_utils;
use actix_files::NamedFile;
use actix_multipart::form::MultipartForm;
use actix_multipart::form::tempfile::TempFile;
use actix_web::{HttpRequest, HttpResponse, web};
use futures_util::StreamExt;
use std::fs::File;
use std::io::Write;
use std::os::unix::fs::MetadataExt;

#[derive(Debug, MultipartForm)]
pub struct UploadIsoForm {
    #[multipart(rename = "file")]
    files: Vec<TempFile>,
}

/// Upload iso file
pub async fn upload_file(MultipartForm(mut form): MultipartForm<UploadIsoForm>) -> HttpResult {
    if form.files.is_empty() {
        log::error!("Missing uploaded ISO file!");
        return Ok(HttpResponse::BadRequest().json("Missing file!"));
    }

    let file = form.files.remove(0);

    if file.size > constants::ISO_MAX_SIZE.as_bytes() {
        log::error!("Uploaded ISO file is too large!");
        return Ok(HttpResponse::BadRequest().json("File is too large!"));
    }

    if let Some(m) = &file.content_type {
        if !constants::ALLOWED_ISO_MIME_TYPES.contains(&m.to_string().as_str()) {
            log::error!("Uploaded ISO file has an invalid mimetype!");
            return Ok(HttpResponse::BadRequest().json("Invalid mimetype!"));
        }
    }

    let file_name = match &file.file_name {
        None => {
            log::error!("Uploaded ISO file does not have a name!");
            return Ok(HttpResponse::BadRequest().json("Missing file name!"));
        }
        Some(f) => f,
    };

    if !files_utils::check_file_name(file_name) {
        log::error!("Bad file name for uploaded iso!");
        return Ok(HttpResponse::BadRequest().json("Bad file name!"));
    }

    let dest_file = AppConfig::get().iso_storage_path().join(file_name);
    log::info!("Will save ISO file {:?}", dest_file);

    if dest_file.exists() {
        log::error!("Conflict with uploaded iso file name!");
        return Ok(HttpResponse::Conflict().json("The file already exists!"));
    }

    file.file.persist(&dest_file)?;

    // Set file permissions
    files_utils::set_file_permission(dest_file, 0o644)?;

    Ok(HttpResponse::Accepted().finish())
}

#[derive(serde::Deserialize)]
pub struct DownloadFromURLReq {
    url: String,
    filename: String,
}

/// Upload ISO file from URL
pub async fn upload_from_url(req: web::Json<DownloadFromURLReq>) -> HttpResult {
    if !files_utils::check_file_name(&req.filename) || !req.filename.ends_with(".iso") {
        return Ok(HttpResponse::BadRequest().json("Invalid file name!"));
    }

    let dest_file = AppConfig::get().iso_storage_path().join(&req.filename);

    if dest_file.exists() {
        return Ok(HttpResponse::Conflict().json("A similar file already exists!"));
    }

    let response = reqwest::get(&req.url).await?;

    if let Some(len) = response.content_length() {
        if len > constants::ISO_MAX_SIZE.as_bytes() as u64 {
            return Ok(HttpResponse::BadRequest().json("File is too large!"));
        }
    }

    if let Some(ct) = response.headers().get("content-type") {
        if !constants::ALLOWED_ISO_MIME_TYPES.contains(&ct.to_str()?) {
            return Ok(HttpResponse::BadRequest().json("Invalid file mimetype!"));
        }
    }

    let mut stream = response.bytes_stream();

    let mut file = File::create(dest_file)?;

    while let Some(item) = stream.next().await {
        let bytes = item?;
        file.write_all(&bytes)?;
    }

    Ok(HttpResponse::Accepted().finish())
}

#[derive(serde::Serialize)]
struct IsoFile {
    filename: String,
    size: u64,
}

/// Get ISO files list
pub async fn get_list() -> HttpResult {
    let mut list = vec![];
    for entry in AppConfig::get().iso_storage_path().read_dir()? {
        let entry = entry?;
        list.push(IsoFile {
            filename: entry.file_name().to_string_lossy().to_string(),
            size: entry.path().metadata()?.size(),
        })
    }

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

#[derive(serde::Deserialize)]
pub struct IsoFilePath {
    filename: String,
}

/// Download ISO file
pub async fn download_file(p: web::Path<IsoFilePath>, req: HttpRequest) -> HttpResult {
    if !files_utils::check_file_name(&p.filename) {
        return Ok(HttpResponse::BadRequest().json("Invalid file name!"));
    }

    let file_path = AppConfig::get().iso_storage_path().join(&p.filename);

    if !file_path.exists() {
        return Ok(HttpResponse::NotFound().json("File does not exists!"));
    }

    Ok(NamedFile::open(file_path)?.into_response(&req))
}

/// Delete ISO file
pub async fn delete_file(p: web::Path<IsoFilePath>) -> HttpResult {
    if !files_utils::check_file_name(&p.filename) {
        return Ok(HttpResponse::BadRequest().json("Invalid file name!"));
    }

    let file_path = AppConfig::get().iso_storage_path().join(&p.filename);

    if !file_path.exists() {
        return Ok(HttpResponse::NotFound().json("File does not exists!"));
    }

    std::fs::remove_file(file_path)?;

    Ok(HttpResponse::Accepted().finish())
}