use std::path::{Path, PathBuf};

#[derive(Debug, Clone)]
pub struct AttachmentMetadata {
    pub attribution_info: Option<AttributionInfo>,
}

#[derive(Debug, Clone)]
pub struct AttributionInfo {
    pub width: Option<u32>,
    pub height: Option<u32>,
}

#[derive(Debug, Clone)]
pub struct Attachment {
    pub guid: String,
    pub base_path: PathBuf,
    pub metadata: Option<AttachmentMetadata>,
    pub mime_type: Option<String>,
}

impl Attachment {
    pub(crate) fn preferred_extension_for_mime(mime: &str) -> Option<&'static str> {
        let normalized = mime.split(';').next().unwrap_or(mime).trim();
        // Prefer common, user-friendly extensions over obscure ones
        match normalized {
            "image/jpeg" | "image/pjpeg" => Some("jpg"),
            _ => mime_guess::get_mime_extensions_str(normalized).and_then(|list| {
                // If jpg is one of the candidates, prefer it
                if list.iter().any(|e| *e == "jpg") {
                    Some("jpg")
                } else {
                    list.first().copied()
                }
            }),
        }
    }
    pub fn get_path(&self) -> PathBuf {
        self.get_path_for_preview_scratch(false, false)
    }

    pub fn get_path_for_preview(&self, preview: bool) -> PathBuf {
        self.get_path_for_preview_scratch(preview, false)
    }

    pub fn get_path_for_preview_scratch(&self, preview: bool, scratch: bool) -> PathBuf {
        // Determine whether this is a preview or full attachment.
        let kind = if preview { "preview" } else { "full" };

        // If not a scratch path, and a file already exists on disk with a concrete
        // file extension (e.g., guid.full.jpg), return that existing path.
        if !scratch {
            if let Some(existing) = self.find_existing_path(preview) {
                return existing;
            }
        }

        // Fall back to constructing a path using known info. If we know the MIME type,
        // prefer an extension guessed from it; otherwise keep legacy naming.
        let ext_from_mime = self
            .mime_type
            .as_ref()
            .and_then(|m| Self::preferred_extension_for_mime(m));

        let base_ext = match ext_from_mime {
            Some(ext) => format!("{}.{}", kind, ext),
            None => kind.to_string(),
        };

        if scratch {
            self.base_path
                .with_extension(format!("{}.download", base_ext))
        } else {
            self.base_path.with_extension(base_ext)
        }
    }

    pub fn is_downloaded(&self, preview: bool) -> bool {
        std::fs::exists(&self.get_path_for_preview(preview)).expect(
            format!(
                "Wasn't able to check for the existence of an attachment file path at {}",
                &self.get_path_for_preview(preview).display()
            )
            .as_str(),
        )
    }

    fn find_existing_path(&self, preview: bool) -> Option<PathBuf> {
        let kind = if preview { "preview" } else { "full" };

        // First, check legacy path without a concrete file extension.
        let legacy = self.base_path.with_extension(kind);
        if legacy.exists() {
            return Some(legacy);
        }

        // Next, search for a filename like: <guid>.<kind>.<ext>
        let file_stem = self
            .base_path
            .file_name()
            .map(|s| s.to_string_lossy().to_string())?;
        let prefix = format!("{}.{}.", file_stem, kind);

        let parent = self.base_path.parent().unwrap_or_else(|| Path::new("."));
        if let Ok(dir) = std::fs::read_dir(parent) {
            for entry in dir.flatten() {
                let file_name = entry.file_name();
                let name = file_name.to_string_lossy();
                if name.starts_with(&prefix) && !name.ends_with(".download") {
                    return Some(entry.path());
                }
            }
        }

        None
    }
}

impl From<kordophone::model::message::AttachmentMetadata> for AttachmentMetadata {
    fn from(metadata: kordophone::model::message::AttachmentMetadata) -> Self {
        Self {
            attribution_info: metadata.attribution_info.map(|info| info.into()),
        }
    }
}

impl From<kordophone::model::message::AttributionInfo> for AttributionInfo {
    fn from(info: kordophone::model::message::AttributionInfo) -> Self {
        Self {
            width: info.width,
            height: info.height,
        }
    }
}

impl From<AttachmentMetadata> for kordophone::model::message::AttachmentMetadata {
    fn from(metadata: AttachmentMetadata) -> Self {
        Self {
            attribution_info: metadata.attribution_info.map(|info| info.into()),
        }
    }
}

impl From<AttributionInfo> for kordophone::model::message::AttributionInfo {
    fn from(info: AttributionInfo) -> Self {
        Self {
            width: info.width,
            height: info.height,
        }
    }
}
