use serde::{Deserialize, Serialize};
use std::fmt::{Debug, Display, Formatter};

#[cfg(all(not(target_arch = "wasm32"), feature = "ffmpeg"))]
use {anyhow::anyhow, anyhow::Context, anyhow::Result, ez_ffmpeg};

#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub enum ApVideoType {
    #[serde(alias = "video")]
    Video,
}

impl Display for ApVideoType {
    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
        Debug::fmt(self, f)
    }
}

#[derive(Serialize, Deserialize, Clone, Eq, PartialEq, Ord, PartialOrd)]
#[serde(rename_all = "camelCase")]
pub struct ApVideo {
    #[serde(rename = "type")]
    pub kind: ApVideoType,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub media_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub width: Option<i32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub height: Option<i32>,

    // filename is used internally to facilitate struct initialization
    #[serde(skip_serializing)]
    pub(crate) filename: Option<String>,

    // path is used internally to facilitate struct initialization
    #[serde(skip_serializing)]
    pub(crate) path: Option<String>,
}

impl ApVideo {
    pub fn initialize(path: String, filename: String, media_type: String) -> Self {
        ApVideo {
            kind: ApVideoType::Video,
            media_type: Some(media_type),
            url: Some(format!("{}/media/uploads/{}", *crate::SERVER_URL, filename)),
            width: None,
            height: None,
            filename: Some(filename),
            path: Some(path),
        }
    }
}

#[cfg(all(not(target_arch = "wasm32"), feature = "ffmpeg"))]
impl ApVideo {
    pub fn analyze(&mut self) -> Result<()> {
        if self.url.is_none() {
            return Err(anyhow!("Unable to analyze ApVideo with empty URL"));
        }

        let full_path = &format!(
            "{}/{}",
            self.path.clone().context("path cannot be None")?,
            self.filename.clone().context("filename cannot be None")?
        );

        match ez_ffmpeg::stream_info::find_video_stream_info(full_path)? {
            Some(ez_ffmpeg::stream_info::StreamInfo::Video { width, height, .. }) => {
                self.width = Some(width);
                self.height = Some(height);
            }
            Some(_) => {
                // This case implies find_video_stream_info returned Some(StreamInfo) 
                // but it wasn't the Video variant, which is unexpected.
                return Err(anyhow!("Retrieved stream info is not for a video stream"));
            }
            None => {
                return Err(anyhow!("No video stream found in file"));
            }
        }
        Ok(())
    }
}

impl Debug for ApVideo {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}[{}{}{}]",
            self.kind,
            self.url.as_deref().unwrap_or("no_url"),
            match (self.width, self.height) {
                (Some(w), Some(h)) => format!(", {}x{}", w, h),
                _ => String::new(),
            },
            self.media_type
                .as_ref()
                .map(|mt| format!(", {}", mt))
                .unwrap_or_default()
        )
    }
}

impl Display for ApVideo {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}[{}]",
            self.kind,
            self.url.as_deref().unwrap_or("no_url"),
        )
    }
}
