//! Multimodal data types for AiFastDb
//!
//! Supports native storage of images, audio, and video alongside text,
//! enabling cross-modal search (e.g., text-to-image, image-to-text).
//!
//! # Supported Formats
//!
//! | Type   | Formats                        | Max Size (default) |
//! |--------|--------------------------------|--------------------|
//! | Image  | JPEG, PNG, WebP, GIF, BMP, SVG | 50 MB              |
//! | Audio  | WAV, MP3, FLAC, OGG, AAC, M4A  | 200 MB             |
//! | Video  | MP4, WebM, AVI, MOV, MKV       | 2 GB               |
//!
//! # Architecture
//!
//! ```text
//! MultimodalRecord
//!   ├── id: String
//!   ├── media_type: MediaType (Image/Audio/Video)
//!   ├── mime_type: String ("image/jpeg", etc.)
//!   ├── data: Vec<u8>  (raw bytes, stored in LargeBinary column)
//!   ├── metadata: MediaMetadata (resolution, duration, etc.)
//!   ├── thumbnail: Option<Vec<u8>> (small preview, max 64KB)
//!   ├── text_content: Option<String> (caption, transcript, description)
//!   └── embeddings: HashMap<String, Vec<f32>> (per-modality vectors)
//! ```

use std::collections::HashMap;
use serde::{Deserialize, Serialize};

// ============================================================================
// Media Type
// ============================================================================

/// Type of media content
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum MediaType {
    /// Image (JPEG, PNG, WebP, GIF, BMP, SVG)
    Image,
    /// Audio (WAV, MP3, FLAC, OGG, AAC, M4A)
    Audio,
    /// Video (MP4, WebM, AVI, MOV, MKV)
    Video,
}

impl MediaType {
    /// Infer media type from MIME type string
    pub fn from_mime(mime: &str) -> Option<Self> {
        let mime_lower = mime.to_lowercase();
        if mime_lower.starts_with("image/") {
            Some(MediaType::Image)
        } else if mime_lower.starts_with("audio/") {
            Some(MediaType::Audio)
        } else if mime_lower.starts_with("video/") {
            Some(MediaType::Video)
        } else {
            None
        }
    }

    /// Infer media type from file extension
    pub fn from_extension(ext: &str) -> Option<Self> {
        match ext.to_lowercase().as_str() {
            // Image formats
            "jpg" | "jpeg" | "png" | "webp" | "gif" | "bmp" | "svg" | "tiff" | "ico" => {
                Some(MediaType::Image)
            }
            // Audio formats
            "wav" | "mp3" | "flac" | "ogg" | "aac" | "m4a" | "wma" | "opus" => {
                Some(MediaType::Audio)
            }
            // Video formats
            "mp4" | "webm" | "avi" | "mov" | "mkv" | "flv" | "wmv" | "m4v" => {
                Some(MediaType::Video)
            }
            _ => None,
        }
    }

    /// Get the default MIME type for this media type
    pub fn default_mime(&self) -> &'static str {
        match self {
            MediaType::Image => "image/jpeg",
            MediaType::Audio => "audio/wav",
            MediaType::Video => "video/mp4",
        }
    }

    /// Default maximum file size in bytes
    pub fn default_max_size_bytes(&self) -> usize {
        match self {
            MediaType::Image => 50 * 1024 * 1024,       // 50 MB
            MediaType::Audio => 200 * 1024 * 1024,      // 200 MB
            MediaType::Video => 2 * 1024 * 1024 * 1024, // 2 GB
        }
    }
}

impl std::fmt::Display for MediaType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            MediaType::Image => write!(f, "image"),
            MediaType::Audio => write!(f, "audio"),
            MediaType::Video => write!(f, "video"),
        }
    }
}

// ============================================================================
// Media Metadata
// ============================================================================

/// Metadata specific to each media type.
///
/// Contains technical properties extracted from the media file
/// (resolution, duration, codec, etc.).
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct MediaMetadata {
    /// Original file name (if available)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub filename: Option<String>,

    /// File size in bytes
    pub file_size_bytes: u64,

    /// MIME type (e.g., "image/jpeg", "audio/mp3", "video/mp4")
    pub mime_type: String,

    // ── Image-specific ──

    /// Image width in pixels
    #[serde(skip_serializing_if = "Option::is_none")]
    pub width: Option<u32>,

    /// Image height in pixels
    #[serde(skip_serializing_if = "Option::is_none")]
    pub height: Option<u32>,

    /// Image color depth (bits per pixel)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub color_depth: Option<u8>,

    // ── Audio/Video-specific ──

    /// Duration in seconds
    #[serde(skip_serializing_if = "Option::is_none")]
    pub duration_secs: Option<f64>,

    /// Sample rate in Hz (audio)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub sample_rate: Option<u32>,

    /// Number of audio channels (1=mono, 2=stereo)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub channels: Option<u8>,

    /// Bitrate in kbps
    #[serde(skip_serializing_if = "Option::is_none")]
    pub bitrate_kbps: Option<u32>,

    /// Codec name (e.g., "h264", "aac", "opus")
    #[serde(skip_serializing_if = "Option::is_none")]
    pub codec: Option<String>,

    /// Video frame rate (fps)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub fps: Option<f32>,

    // ── General ──

    /// Additional metadata as key-value pairs
    #[serde(default)]
    #[serde(skip_serializing_if = "HashMap::is_empty")]
    pub extra: HashMap<String, serde_json::Value>,
}

impl MediaMetadata {
    /// Create metadata for an image
    pub fn image(mime_type: &str, file_size: u64, width: u32, height: u32) -> Self {
        Self {
            mime_type: mime_type.to_string(),
            file_size_bytes: file_size,
            width: Some(width),
            height: Some(height),
            ..Default::default()
        }
    }

    /// Create metadata for an audio file
    pub fn audio(mime_type: &str, file_size: u64, duration_secs: f64) -> Self {
        Self {
            mime_type: mime_type.to_string(),
            file_size_bytes: file_size,
            duration_secs: Some(duration_secs),
            ..Default::default()
        }
    }

    /// Create metadata for a video file
    pub fn video(
        mime_type: &str,
        file_size: u64,
        width: u32,
        height: u32,
        duration_secs: f64,
    ) -> Self {
        Self {
            mime_type: mime_type.to_string(),
            file_size_bytes: file_size,
            width: Some(width),
            height: Some(height),
            duration_secs: Some(duration_secs),
            ..Default::default()
        }
    }
}

// ============================================================================
// Multimodal Record
// ============================================================================

/// A multimodal record that can hold image, audio, or video data
/// alongside text and vector embeddings.
///
/// This is the primary unit for storing and searching multimodal data.
///
/// # Example
///
/// ```
/// use aifastdb_core::types::multimodal::{MultimodalRecord, MediaType, MediaMetadata};
///
/// let record = MultimodalRecord::new("img-001", MediaType::Image)
///     .with_data(jpeg_bytes)
///     .with_mime_type("image/jpeg")
///     .with_text_content("A cat sitting on a window sill")
///     .with_metadata(MediaMetadata::image("image/jpeg", 102400, 1920, 1080))
///     .with_tag("cat")
///     .with_tag("indoor");
/// ```
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MultimodalRecord {
    /// Unique identifier
    pub id: String,

    /// Media type (Image/Audio/Video)
    pub media_type: MediaType,

    /// MIME type (e.g., "image/jpeg")
    pub mime_type: String,

    /// Raw binary data (stored in LargeBinary column)
    ///
    /// For large files, consider streaming upload instead of loading all into memory.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub data: Option<Vec<u8>>,

    /// Thumbnail/preview image (small JPEG, max ~64KB)
    ///
    /// Auto-generated for images (downscaled) and videos (first frame).
    /// For audio, may contain a waveform visualization.
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub thumbnail: Option<Vec<u8>>,

    /// Text content associated with this media:
    /// - Image: caption, alt text, OCR result
    /// - Audio: transcript (from speech-to-text)
    /// - Video: subtitle text, scene description
    #[serde(skip_serializing_if = "Option::is_none")]
    #[serde(default)]
    pub text_content: Option<String>,

    /// Technical metadata (resolution, duration, codec, etc.)
    pub metadata: MediaMetadata,

    /// User-defined tags for categorization
    #[serde(default)]
    pub tags: Vec<String>,

    /// User-defined key-value metadata
    #[serde(default)]
    pub user_metadata: HashMap<String, serde_json::Value>,

    /// Per-modality embeddings
    ///
    /// Key is the embedding model/modality name:
    /// - "clip_image": CLIP image embedding (512d)
    /// - "clip_text": CLIP text embedding (512d) — from caption
    /// - "text_embedding": standard text embedding (from text_content)
    /// - "audio_embedding": audio feature embedding
    #[serde(default)]
    #[serde(skip_serializing_if = "HashMap::is_empty")]
    pub embeddings: HashMap<String, Vec<f32>>,

    /// Creation timestamp (Unix milliseconds)
    pub created_at: i64,

    /// Last access timestamp (Unix milliseconds)
    pub last_accessed_at: i64,
}

impl MultimodalRecord {
    /// Create a new multimodal record with the given ID and media type.
    pub fn new(id: impl Into<String>, media_type: MediaType) -> Self {
        let now = std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .unwrap_or_default()
            .as_millis() as i64;

        Self {
            id: id.into(),
            media_type,
            mime_type: media_type.default_mime().to_string(),
            data: None,
            thumbnail: None,
            text_content: None,
            metadata: MediaMetadata::default(),
            tags: Vec::new(),
            user_metadata: HashMap::new(),
            embeddings: HashMap::new(),
            created_at: now,
            last_accessed_at: now,
        }
    }

    /// Set the raw binary data.
    pub fn with_data(mut self, data: Vec<u8>) -> Self {
        self.metadata.file_size_bytes = data.len() as u64;
        self.data = Some(data);
        self
    }

    /// Set the MIME type.
    pub fn with_mime_type(mut self, mime: impl Into<String>) -> Self {
        let mime_str = mime.into();
        self.metadata.mime_type = mime_str.clone();
        self.mime_type = mime_str;
        self
    }

    /// Set the text content (caption, transcript, etc.)
    pub fn with_text_content(mut self, text: impl Into<String>) -> Self {
        self.text_content = Some(text.into());
        self
    }

    /// Set a thumbnail/preview.
    pub fn with_thumbnail(mut self, thumb: Vec<u8>) -> Self {
        self.thumbnail = Some(thumb);
        self
    }

    /// Set media metadata.
    pub fn with_metadata(mut self, metadata: MediaMetadata) -> Self {
        self.metadata = metadata;
        self
    }

    /// Add a tag.
    pub fn with_tag(mut self, tag: impl Into<String>) -> Self {
        self.tags.push(tag.into());
        self
    }

    /// Add user metadata.
    pub fn with_user_metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
        self.user_metadata.insert(key.into(), value);
        self
    }

    /// Add an embedding for a specific modality.
    pub fn with_embedding(mut self, modality: impl Into<String>, embedding: Vec<f32>) -> Self {
        self.embeddings.insert(modality.into(), embedding);
        self
    }

    /// Check if this record has binary data loaded.
    pub fn has_data(&self) -> bool {
        self.data.is_some()
    }

    /// Get the file size in bytes.
    pub fn file_size(&self) -> u64 {
        self.data.as_ref().map(|d| d.len() as u64).unwrap_or(self.metadata.file_size_bytes)
    }

    /// Get a human-readable file size string.
    pub fn file_size_human(&self) -> String {
        let bytes = self.file_size();
        if bytes >= 1 << 30 {
            format!("{:.2} GB", bytes as f64 / (1u64 << 30) as f64)
        } else if bytes >= 1 << 20 {
            format!("{:.2} MB", bytes as f64 / (1u64 << 20) as f64)
        } else if bytes >= 1 << 10 {
            format!("{:.2} KB", bytes as f64 / (1u64 << 10) as f64)
        } else {
            format!("{} B", bytes)
        }
    }
}

// ============================================================================
// Multimodal Storage Configuration
// ============================================================================

/// Configuration for multimodal data storage.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MultimodalConfig {
    /// Maximum file size per media type (in bytes).
    /// If not set, uses MediaType::default_max_size_bytes().
    #[serde(default)]
    pub max_sizes: HashMap<String, usize>,

    /// Whether to auto-generate thumbnails on write (default: true)
    #[serde(default = "default_true")]
    pub auto_thumbnail: bool,

    /// Thumbnail max dimension in pixels (default: 256)
    #[serde(default = "default_thumbnail_size")]
    pub thumbnail_max_dim: u32,

    /// Whether to auto-generate embeddings on write (default: true)
    #[serde(default = "default_true")]
    pub auto_embed: bool,

    /// Embedding model name for images (default: "clip-vit-b32")
    #[serde(default = "default_clip_model")]
    pub image_embedding_model: String,

    /// Embedding model name for text captions (default: uses default text model)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub text_embedding_model: Option<String>,

    /// Whether to store data inline (in Lance table) or as external blobs
    #[serde(default = "default_true")]
    pub inline_storage: bool,
}

fn default_true() -> bool {
    true
}

fn default_thumbnail_size() -> u32 {
    256
}

fn default_clip_model() -> String {
    "clip-vit-b32".to_string()
}

impl Default for MultimodalConfig {
    fn default() -> Self {
        Self {
            max_sizes: HashMap::new(),
            auto_thumbnail: true,
            thumbnail_max_dim: 256,
            auto_embed: true,
            image_embedding_model: "clip-vit-b32".to_string(),
            text_embedding_model: None,
            inline_storage: true,
        }
    }
}

// ============================================================================
// Multimodal Vector Column Configuration
// ============================================================================

/// Definition of a single native vector column in the multimodal table.
///
/// Maps an embedding name (e.g., "clip_image") to a Lance native
/// FixedSizeList(Float32, dim) column for ANN index support.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VectorColumnDef {
    /// Lance column name (e.g., "clip_image_vec")
    pub column_name: String,
    /// Vector dimension (e.g., 512 for CLIP ViT-B/32)
    pub dimension: usize,
}

impl VectorColumnDef {
    pub fn new(column_name: impl Into<String>, dimension: usize) -> Self {
        Self {
            column_name: column_name.into(),
            dimension,
        }
    }
}

/// Configuration for native vector columns in multimodal tables.
///
/// Controls which embeddings get stored as Lance native vector columns
/// (FixedSizeList(Float32, dim)) in addition to the `embeddings_json` column.
/// Native vector columns enable Lance ANN indexing and ~100-1000x faster search.
///
/// # Example
///
/// ```
/// use aifastdb_core::types::multimodal::{MultimodalVectorConfig, VectorColumnDef};
///
/// let config = MultimodalVectorConfig::new()
///     .with_column("clip_image", VectorColumnDef::new("clip_image_vec", 512))
///     .with_column("clip_text", VectorColumnDef::new("clip_text_vec", 512));
/// ```
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct MultimodalVectorConfig {
    /// Mapping from embedding name → native vector column definition.
    ///
    /// Key: embedding name as stored in `MultimodalRecord.embeddings` (e.g., "clip_image")
    /// Value: column definition with Lance column name and dimension
    #[serde(default)]
    pub columns: HashMap<String, VectorColumnDef>,
}

impl MultimodalVectorConfig {
    /// Create an empty config.
    pub fn new() -> Self {
        Self::default()
    }

    /// Add a vector column mapping.
    pub fn with_column(mut self, embedding_name: impl Into<String>, def: VectorColumnDef) -> Self {
        self.columns.insert(embedding_name.into(), def);
        self
    }

    /// Create a standard CLIP config with given dimension.
    ///
    /// Sets up columns for `clip_image` and `clip_text` embeddings.
    pub fn clip(dimension: usize) -> Self {
        Self::new()
            .with_column("clip_image", VectorColumnDef::new("clip_image_vec", dimension))
            .with_column("clip_text", VectorColumnDef::new("clip_text_vec", dimension))
    }

    /// Resolve the native vector column name for a given embedding name.
    ///
    /// Returns `None` if no native column is configured for this embedding.
    pub fn resolve_column(&self, embedding_name: &str) -> Option<&VectorColumnDef> {
        self.columns.get(embedding_name)
    }

    /// Check if any native vector columns are configured.
    pub fn has_columns(&self) -> bool {
        !self.columns.is_empty()
    }
}

/// Information about a multimodal vector index.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MultimodalIndexInfo {
    /// Embedding name (e.g., "clip_image")
    pub embedding_name: String,
    /// Native column name (e.g., "clip_image_vec")
    pub column_name: String,
    /// Vector dimension
    pub dimension: usize,
    /// Whether a vector index exists on this column
    pub has_index: bool,
    /// Number of indexed vectors (if known)
    pub num_vectors: Option<usize>,
}

// ============================================================================
// Multimodal Backend Trait
// ============================================================================

/// Trait for multimodal data storage operations.
///
/// Provides CRUD operations for storing and retrieving binary media data
/// (images, audio, video) alongside text and vector embeddings.
pub trait MultimodalBackend: Send + Sync {
    /// Store a multimodal record (including binary data).
    ///
    /// If a record with the same ID exists, it is overwritten.
    fn put_multimodal(
        &self,
        record: &MultimodalRecord,
    ) -> Result<(), crate::storage::backend::StorageError>;

    /// Retrieve a multimodal record by ID.
    ///
    /// If `include_data` is false, the binary `data` field is omitted
    /// (only metadata, text, and embeddings are returned).
    fn get_multimodal(
        &self,
        id: &str,
        include_data: bool,
    ) -> Result<Option<MultimodalRecord>, crate::storage::backend::StorageError>;

    /// Delete a multimodal record.
    fn delete_multimodal(
        &self,
        id: &str,
    ) -> Result<bool, crate::storage::backend::StorageError>;

    /// List all multimodal record IDs, optionally filtered by media type.
    fn list_multimodal(
        &self,
        media_type: Option<MediaType>,
        limit: usize,
        offset: usize,
    ) -> Result<Vec<String>, crate::storage::backend::StorageError>;

    /// Get the count of multimodal records, optionally filtered by media type.
    fn multimodal_count(
        &self,
        media_type: Option<MediaType>,
    ) -> Result<usize, crate::storage::backend::StorageError>;

    /// Search multimodal records by vector similarity.
    ///
    /// Performs cross-modal search: the query vector can come from any modality
    /// (text or image via CLIP), and matching records are returned ranked by
    /// cosine similarity.
    ///
    /// # Search Strategy
    ///
    /// If a native vector column exists and has an ANN index, search uses
    /// Lance `scanner.nearest()` for O(log N) performance. Otherwise, falls
    /// back to brute-force JSON scan.
    ///
    /// # Arguments
    /// * `query_vector` - The embedding vector to search with
    /// * `embedding_column` - Which embedding to search against (e.g., "clip_image")
    /// * `top_k` - Maximum number of results
    /// * `media_type` - Optional filter by media type
    fn search_multimodal(
        &self,
        query_vector: &[f32],
        embedding_column: &str,
        top_k: usize,
        media_type: Option<MediaType>,
    ) -> Result<Vec<CrossModalSearchResult>, crate::storage::backend::StorageError>;

    // ── Vector Index Management (Phase-60) ──

    /// Create a vector index on a native multimodal embedding column.
    ///
    /// Requires that the embedding has a corresponding native vector column
    /// configured in `MultimodalVectorConfig`. If GPU is configured in the
    /// index config, GPU acceleration is used automatically.
    ///
    /// Default implementation returns `NotImplemented`.
    fn create_multimodal_index(
        &self,
        _embedding_name: &str,
        _config: &crate::storage::vector_backend::VectorIndexConfig,
    ) -> Result<(), crate::storage::backend::StorageError> {
        Err(crate::storage::backend::StorageError::Backend(
            "create_multimodal_index not implemented for this backend".into(),
        ))
    }

    /// Check if a vector index exists for the given embedding.
    ///
    /// Default implementation returns `false`.
    fn has_multimodal_index(
        &self,
        _embedding_name: &str,
    ) -> Result<bool, crate::storage::backend::StorageError> {
        Ok(false)
    }

    /// List all multimodal vector columns and their index status.
    ///
    /// Default implementation returns an empty vec.
    fn multimodal_index_info(
        &self,
    ) -> Result<Vec<MultimodalIndexInfo>, crate::storage::backend::StorageError> {
        Ok(vec![])
    }
}

// ============================================================================
// Cross-Modal Search Types
// ============================================================================

/// Result from a cross-modal similarity search.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrossModalSearchResult {
    /// ID of the matched multimodal record
    pub id: String,
    /// Media type of the matched record
    pub media_type: MediaType,
    /// Similarity score (0.0 to 1.0, higher is better)
    pub score: f32,
    /// MIME type
    pub mime_type: String,
    /// Text content (caption, transcript)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub text_content: Option<String>,
    /// File size in bytes
    pub file_size_bytes: u64,
    /// Whether thumbnail is available
    pub has_thumbnail: bool,
    /// Tags
    pub tags: Vec<String>,
}

/// Query type for cross-modal search.
#[derive(Debug, Clone)]
pub enum CrossModalQuery {
    /// Text query — encode text with CLIP, search across all media
    Text(String),
    /// Image query — encode image with CLIP, search for similar images/text
    ImageBytes(Vec<u8>),
    /// Raw vector query — directly provide the embedding vector
    Vector(Vec<f32>),
}
