use pathbufd::PathBufD;
use serde::{Deserialize, Serialize};
use std::{
    collections::HashMap,
    fs::{exists, remove_file, write},
};
use tetratto_core::model::{Error, Result};
use tetratto_shared::{snow::Snowflake, unix_epoch_timestamp};

#[derive(Serialize, Deserialize, PartialEq, Eq)]
pub enum MediaType {
    #[serde(alias = "image/webp")]
    Webp,
    #[serde(alias = "image/avif")]
    Avif,
    #[serde(alias = "image/png")]
    Png,
    #[serde(alias = "image/jpg")]
    Jpg,
    #[serde(alias = "image/gif")]
    Gif,
    #[serde(alias = "image/carpgraph")]
    Carpgraph,
}

impl MediaType {
    pub fn extension(&self) -> &str {
        match self {
            Self::Webp => "webp",
            Self::Avif => "avif",
            Self::Png => "png",
            Self::Jpg => "jpg",
            Self::Gif => "gif",
            Self::Carpgraph => "carpgraph",
        }
    }

    pub fn mime(&self) -> String {
        format!("image/{}", self.extension())
    }
}

#[derive(Serialize, Deserialize)]
pub struct UploadMetadata {
    pub what: MediaType,
    #[serde(default)]
    pub alt: String,
    #[serde(default)]
    pub kv: HashMap<String, String>,
}

impl UploadMetadata {
    pub fn validate_kv(&self) -> Result<()> {
        for x in &self.kv {
            if x.0.len() > 32 {
                return Err(Error::DataTooLong("key".to_string()));
            }

            if x.1.len() > 128 {
                return Err(Error::DataTooLong("value".to_string()));
            }
        }

        Ok(())
    }
}

#[derive(Serialize, Deserialize)]
pub struct MediaUpload {
    pub id: usize,
    pub created: usize,
    pub owner: usize,
    pub bucket: String,
    pub metadata: UploadMetadata,
}

impl MediaUpload {
    /// Create a new [`MediaUpload`].
    pub fn new(what: MediaType, owner: usize, bucket: String) -> Self {
        Self {
            id: Snowflake::new().to_string().parse::<usize>().unwrap(),
            created: unix_epoch_timestamp(),
            owner,
            bucket,
            metadata: UploadMetadata {
                alt: String::new(),
                what,
                kv: HashMap::new(),
            },
        }
    }

    /// Get the path to the fs file for this upload.
    pub fn path(&self, directory: &str) -> PathBufD {
        PathBufD::current().extend(&[
            directory,
            &format!("{}.{}", self.id, self.metadata.what.extension()),
        ])
    }

    /// Write to this upload in the file system.
    pub fn write(&self, directory: &str, bytes: &[u8]) -> Result<()> {
        match write(self.path(directory), bytes) {
            Ok(_) => Ok(()),
            Err(e) => Err(Error::MiscError(e.to_string())),
        }
    }

    /// Delete this upload in the file system.
    pub fn remove(&self, directory: &str) -> Result<()> {
        let path = self.path(directory);

        if !exists(&path).unwrap() {
            return Ok(());
        }

        match remove_file(path) {
            Ok(_) => Ok(()),
            Err(e) => Err(Error::MiscError(e.to_string())),
        }
    }
}
