use crate::file_kind::FileKind;
use actix_multipart::{Field, Multipart};
use actix_web::{error, http::header::DispositionParam};
use async_std::{fs, fs::File, path::Path, prelude::*};
use chrono::{prelude::*, Duration};
use futures::{StreamExt, TryStreamExt};

pub(crate) struct UploadConfig {
    pub original_name: String,
    pub valid_till: DateTime<Local>,
    pub kind: FileKind,
    pub delete_on_download: bool,
}

pub(crate) async fn parse_multipart(
    mut payload: Multipart,
    file_id: &str,
    filename: &Path,
    max_size: Option<u64>,
) -> Result<UploadConfig, error::Error> {
    let mut original_name: Option<String> = None;
    let mut keep_for: Option<String> = None;
    let mut kind: Option<FileKind> = None;
    let mut delete_on_download = false;

    while let Ok(Some(field)) = payload.try_next().await {
        let name = get_field_name(&field)?;
        let name = name.as_str();
        match name {
            "keep_for" => {
                keep_for = Some(parse_string(name, field).await?);
            }
            "file" => {
                let file_original_name = get_original_filename(&field);
                if file_original_name == None || file_original_name.as_deref() == Some("") {
                    continue;
                }
                original_name = file_original_name;
                kind = Some(FileKind::Binary);
                let mut file = fs::File::create(&filename)
                    .await
                    .map_err(|_| error::ErrorInternalServerError("could not create file"))?;
                write_to_file(&mut file, field, max_size).await?;
            }
            "text" => {
                if original_name.is_some() {
                    continue;
                }
                original_name = Some(format!("{}.txt", file_id));
                kind = Some(FileKind::Text);
                let mut file = fs::File::create(&filename)
                    .await
                    .map_err(|_| error::ErrorInternalServerError("could not create file"))?;
                write_to_file(&mut file, field, max_size).await?;
            }
            "delete_on_download" => {
                delete_on_download = dbg!(parse_string(name, field).await?) != "false";
            }
            _ => {}
        };
    }

    let original_name = original_name.ok_or_else(|| error::ErrorBadRequest("no content found"))?;
    let kind = kind.ok_or_else(|| error::ErrorBadRequest("no content found"))?;

    if original_name.len() > 255 {
        return Err(error::ErrorBadRequest("filename is too long"));
    }
    let valid_till = if let Some(keep_for) = keep_for {
        let keep_for = keep_for.parse().map_err(|e| {
            error::ErrorBadRequest(format!("field keep_for is not a number: {}", e))
        })?;
        let max_keep_for = Duration::days(31).num_seconds();
        if keep_for > max_keep_for {
            return Err(error::ErrorBadRequest(format!(
                "maximum allowed validity is {} seconds, but you specified {} seconds",
                max_keep_for, keep_for
            )));
        }
        Local::now() + Duration::seconds(keep_for)
    } else {
        Local::now() + Duration::seconds(1800)
    };

    Ok(UploadConfig {
        original_name,
        valid_till,
        kind,
        delete_on_download,
    })
}

fn get_field_name(field: &Field) -> Result<String, error::Error> {
    Ok(field
        .content_disposition()
        .ok_or(error::ParseError::Incomplete)?
        .get_name()
        .map(|s| s.to_owned())
        .ok_or(error::ParseError::Incomplete)?)
}

async fn parse_string(name: &str, field: actix_multipart::Field) -> Result<String, error::Error> {
    let data = read_content(field).await?;
    String::from_utf8(data)
        .map_err(|_| error::ErrorBadRequest(format!("could not parse field {} as utf-8", name)))
}

async fn read_content(mut field: actix_multipart::Field) -> Result<Vec<u8>, error::Error> {
    let mut data = Vec::new();
    while let Some(chunk) = field.next().await {
        data.extend(chunk.map_err(error::ErrorBadRequest)?);
    }
    Ok(data)
}

async fn write_to_file(
    file: &mut File,
    mut field: actix_multipart::Field,
    max_size: Option<u64>,
) -> Result<(), error::Error> {
    let mut written_bytes: u64 = 0;
    while let Some(chunk) = field.next().await {
        let chunk = chunk.map_err(error::ErrorBadRequest)?;
        if let Some(max_size) = max_size {
            written_bytes += chunk.len() as u64;
            if written_bytes > max_size {
                return Err(error::ErrorBadRequest(format!(
                    "exceeded maximum file size of {} bytes",
                    max_size
                )));
            }
        }
        file.write_all(chunk.as_ref())
            .await
            .map_err(|_| error::ErrorInternalServerError("could not write file"))?;
    }
    Ok(())
}

fn get_original_filename(field: &actix_multipart::Field) -> Option<String> {
    field.content_disposition().and_then(|content_disposition| {
        content_disposition
            .parameters
            .into_iter()
            .find_map(|param| match param {
                DispositionParam::Filename(filename) => Some(filename),
                _ => None,
            })
    })
}