//! Attachment handling for the Gemini SDK
//!
//! This module provides utilities for handling file attachments in messages,
//! including automatic MIME type detection and file validation.

use std::path::Path;
use serde::{Deserialize, Serialize};

use crate::error::{Result, SDKError};

/// Represents a file attachment for a message
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Attachment {
    /// Path to the file
    pub path: String,
    /// MIME type of the file (auto-detected if not provided)
    pub mime_type: Option<String>,
}

impl Attachment {
    /// Create a new attachment with automatic MIME type detection
    pub fn new(path: impl Into<String>) -> Self {
        let path = path.into();
        let mime_type = detect_mime_type(&path);
        
        Self {
            path,
            mime_type,
        }
    }

    /// Create a new attachment with explicit MIME type
    pub fn with_mime_type(path: impl Into<String>, mime_type: impl Into<String>) -> Self {
        Self {
            path: path.into(),
            mime_type: Some(mime_type.into()),
        }
    }

    /// Get the file extension
    pub fn extension(&self) -> Option<&str> {
        Path::new(&self.path)
            .extension()
            .and_then(|ext| ext.to_str())
    }

    /// Get the file name
    pub fn file_name(&self) -> Option<&str> {
        Path::new(&self.path)
            .file_name()
            .and_then(|name| name.to_str())
    }

    /// Get the effective MIME type (detected or provided)
    pub fn effective_mime_type(&self) -> Option<&str> {
        self.mime_type.as_deref()
    }

    /// Check if this is an image attachment
    pub fn is_image(&self) -> bool {
        self.effective_mime_type()
            .map(|mime| mime.starts_with("image/"))
            .unwrap_or(false)
    }

    /// Check if this is a video attachment
    pub fn is_video(&self) -> bool {
        self.effective_mime_type()
            .map(|mime| mime.starts_with("video/"))
            .unwrap_or(false)
    }

    /// Check if this is an audio attachment
    pub fn is_audio(&self) -> bool {
        self.effective_mime_type()
            .map(|mime| mime.starts_with("audio/"))
            .unwrap_or(false)
    }

    /// Check if this is a text attachment
    pub fn is_text(&self) -> bool {
        self.effective_mime_type()
            .map(|mime| mime.starts_with("text/"))
            .unwrap_or(false)
    }

    /// Validate the attachment
    pub fn validate(&self) -> Result<()> {
        // Check if file exists
        if !Path::new(&self.path).exists() {
            return Err(SDKError::attachment(format!(
                "File does not exist: {}",
                self.path
            )));
        }

        // Check if it's a file (not a directory)
        if !Path::new(&self.path).is_file() {
            return Err(SDKError::attachment(format!(
                "Path is not a file: {}",
                self.path
            )));
        }

        // Check file size (limit to 100MB)
        let metadata = std::fs::metadata(&self.path)
            .map_err(|e| SDKError::attachment(format!("Cannot read file metadata: {}", e)))?;
        
        const MAX_FILE_SIZE: u64 = 100 * 1024 * 1024; // 100MB
        if metadata.len() > MAX_FILE_SIZE {
            return Err(SDKError::attachment(format!(
                "File too large: {} bytes (max: {} bytes)",
                metadata.len(),
                MAX_FILE_SIZE
            )));
        }

        // Validate MIME type if provided
        if let Some(mime_type) = &self.mime_type {
            if !is_supported_mime_type(mime_type) {
                return Err(SDKError::attachment(format!(
                    "Unsupported MIME type: {}",
                    mime_type
                )));
            }
        }

        Ok(())
    }

    /// Get file size in bytes
    pub fn file_size(&self) -> Result<u64> {
        let metadata = std::fs::metadata(&self.path)
            .map_err(|e| SDKError::attachment(format!("Cannot read file metadata: {}", e)))?;
        Ok(metadata.len())
    }
}

/// Detect MIME type based on file extension
fn detect_mime_type(path: &str) -> Option<String> {
    let extension = Path::new(path)
        .extension()
        .and_then(|ext| ext.to_str())
        .map(|ext| ext.to_lowercase())?;

    let mime_type = match extension.as_str() {
        // Images
        "jpg" | "jpeg" => "image/jpeg",
        "png" => "image/png",
        "gif" => "image/gif",
        "webp" => "image/webp",
        "bmp" => "image/bmp",
        "svg" => "image/svg+xml",
        "ico" => "image/x-icon",

        // Videos
        "mp4" => "video/mp4",
        "avi" => "video/x-msvideo",
        "mov" => "video/quicktime",
        "wmv" => "video/x-ms-wmv",
        "flv" => "video/x-flv",
        "webm" => "video/webm",
        "mkv" => "video/x-matroska",
        "m4v" => "video/x-m4v",

        // Audio
        "mp3" => "audio/mpeg",
        "wav" => "audio/wav",
        "ogg" => "audio/ogg",
        "flac" => "audio/flac",
        "aac" => "audio/aac",
        "m4a" => "audio/mp4",

        // Text
        "txt" => "text/plain",
        "md" => "text/markdown",
        "html" => "text/html",
        "css" => "text/css",
        "js" => "text/javascript",
        "json" => "application/json",
        "xml" => "text/xml",

        // Documents
        "pdf" => "application/pdf",
        "doc" => "application/msword",
        "docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        "xls" => "application/vnd.ms-excel",
        "xlsx" => "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
        "ppt" => "application/vnd.ms-powerpoint",
        "pptx" => "application/vnd.openxmlformats-officedocument.presentationml.presentation",

        _ => return None,
    };

    Some(mime_type.to_string())
}

/// Check if a MIME type is supported by Gemini
fn is_supported_mime_type(mime_type: &str) -> bool {
    matches!(
        mime_type,
        // Images
        "image/jpeg" | "image/png" | "image/gif" | "image/webp" | "image/bmp" |
        // Videos
        "video/mp4" | "video/x-msvideo" | "video/quicktime" | "video/webm" |
        // Audio
        "audio/mpeg" | "audio/wav" | "audio/ogg" | "audio/flac" |
        // Text
        "text/plain" | "text/markdown" | "text/html" | "application/json"
    )
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_attachment_creation() {
        let attachment = Attachment::new("test.jpg");
        assert_eq!(attachment.path, "test.jpg");
        assert_eq!(attachment.effective_mime_type(), Some("image/jpeg"));
        assert!(attachment.is_image());
        assert!(!attachment.is_video());
    }

    #[test]
    fn test_attachment_with_mime_type() {
        let attachment = Attachment::with_mime_type("test.unknown", "image/jpeg");
        assert_eq!(attachment.effective_mime_type(), Some("image/jpeg"));
        assert!(attachment.is_image());
    }

    #[test]
    fn test_mime_type_detection() {
        assert_eq!(detect_mime_type("test.jpg"), Some("image/jpeg".to_string()));
        assert_eq!(detect_mime_type("test.png"), Some("image/png".to_string()));
        assert_eq!(detect_mime_type("test.mp4"), Some("video/mp4".to_string()));
        assert_eq!(detect_mime_type("test.mp3"), Some("audio/mpeg".to_string()));
        assert_eq!(detect_mime_type("test.txt"), Some("text/plain".to_string()));
        assert_eq!(detect_mime_type("test.unknown"), None);
    }

    #[test]
    fn test_file_properties() {
        let attachment = Attachment::new("path/to/test.jpg");
        assert_eq!(attachment.extension(), Some("jpg"));
        assert_eq!(attachment.file_name(), Some("test.jpg"));
    }

    #[test]
    fn test_attachment_validation() {
        // Test with non-existent file
        let attachment = Attachment::new("non_existent_file.jpg");
        assert!(attachment.validate().is_err());
    }

    #[test]
    fn test_supported_mime_types() {
        assert!(is_supported_mime_type("image/jpeg"));
        assert!(is_supported_mime_type("video/mp4"));
        assert!(is_supported_mime_type("audio/mpeg"));
        assert!(is_supported_mime_type("text/plain"));
        assert!(!is_supported_mime_type("application/octet-stream"));
    }
}
