//! Workspace metadata storage.

use std::collections::HashMap;
use std::path::PathBuf;

use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

use crate::types::{DocumentId, Language, WorkspaceId};

/// Metadata about a workspace index.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WorkspaceMetadata {
    /// Workspace identifier.
    pub workspace_id: WorkspaceId,
    /// Root path of the workspace.
    pub root_path: PathBuf,
    /// When the workspace was first indexed.
    pub created_at: DateTime<Utc>,
    /// When the workspace was last updated.
    pub updated_at: DateTime<Utc>,
    /// Number of indexed documents.
    pub document_count: usize,
    /// Number of indexed chunks.
    pub chunk_count: usize,
    /// Total size of indexed files in bytes.
    pub total_bytes: u64,
    /// Embedding dimension used.
    pub embedding_dimension: usize,
    /// Version of the index format.
    pub index_version: u32,
    /// Document states for incremental updates.
    pub document_states: HashMap<PathBuf, DocumentState>,
    /// Language statistics.
    pub language_stats: HashMap<Language, LanguageInfo>,
}

impl WorkspaceMetadata {
    /// Create new metadata for a workspace.
    pub fn new(workspace_id: WorkspaceId, root_path: PathBuf, embedding_dimension: usize) -> Self {
        let now = Utc::now();
        Self {
            workspace_id,
            root_path,
            created_at: now,
            updated_at: now,
            document_count: 0,
            chunk_count: 0,
            total_bytes: 0,
            embedding_dimension,
            index_version: CURRENT_INDEX_VERSION,
            document_states: HashMap::new(),
            language_stats: HashMap::new(),
        }
    }

    /// Update timestamp.
    pub fn touch(&mut self) {
        self.updated_at = Utc::now();
    }

    /// Check if a document needs reindexing.
    pub fn needs_reindex(&self, path: &PathBuf, content_hash: &str) -> bool {
        match self.document_states.get(path) {
            Some(state) => state.content_hash != content_hash,
            None => true,
        }
    }

    /// Record a document as indexed.
    pub fn record_document(
        &mut self,
        path: PathBuf,
        document_id: DocumentId,
        content_hash: String,
        size_bytes: u64,
        language: Language,
        chunk_count: usize,
    ) {
        self.document_states.insert(
            path,
            DocumentState {
                document_id,
                content_hash,
                size_bytes,
                language,
                chunk_count,
                indexed_at: Utc::now(),
            },
        );

        // Update language stats
        let lang_info = self.language_stats.entry(language).or_insert(LanguageInfo {
            file_count: 0,
            chunk_count: 0,
            total_bytes: 0,
        });
        lang_info.file_count += 1;
        lang_info.chunk_count += chunk_count;
        lang_info.total_bytes += size_bytes;

        self.document_count = self.document_states.len();
        self.chunk_count = self.document_states.values().map(|s| s.chunk_count).sum();
        self.total_bytes = self.document_states.values().map(|s| s.size_bytes).sum();
        self.touch();
    }

    /// Remove a document from the index.
    pub fn remove_document(&mut self, path: &PathBuf) {
        if let Some(state) = self.document_states.remove(path) {
            // Update language stats
            if let Some(lang_info) = self.language_stats.get_mut(&state.language) {
                lang_info.file_count = lang_info.file_count.saturating_sub(1);
                lang_info.chunk_count = lang_info.chunk_count.saturating_sub(state.chunk_count);
                lang_info.total_bytes = lang_info.total_bytes.saturating_sub(state.size_bytes);

                if lang_info.file_count == 0 {
                    self.language_stats.remove(&state.language);
                }
            }

            self.document_count = self.document_states.len();
            self.chunk_count = self.document_states.values().map(|s| s.chunk_count).sum();
            self.total_bytes = self.document_states.values().map(|s| s.size_bytes).sum();
            self.touch();
        }
    }

    /// Get paths that have been deleted from disk.
    pub fn find_deleted_documents(&self, existing_paths: &[PathBuf]) -> Vec<PathBuf> {
        let existing_set: std::collections::HashSet<_> = existing_paths.iter().collect();
        self.document_states
            .keys()
            .filter(|path| !existing_set.contains(path))
            .cloned()
            .collect()
    }

    /// Check if the index version is compatible.
    pub fn is_compatible(&self) -> bool {
        self.index_version == CURRENT_INDEX_VERSION
    }
}

/// State of a single document in the index.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DocumentState {
    /// Document identifier.
    pub document_id: DocumentId,
    /// Content hash for change detection.
    pub content_hash: String,
    /// File size in bytes.
    pub size_bytes: u64,
    /// Programming language.
    pub language: Language,
    /// Number of chunks extracted.
    pub chunk_count: usize,
    /// When this document was indexed.
    pub indexed_at: DateTime<Utc>,
}

/// Statistics for a single language.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LanguageInfo {
    /// Number of files.
    pub file_count: usize,
    /// Number of chunks.
    pub chunk_count: usize,
    /// Total bytes.
    pub total_bytes: u64,
}

/// Current index format version.
pub const CURRENT_INDEX_VERSION: u32 = 1;

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

    #[test]
    fn test_workspace_metadata() {
        let mut meta = WorkspaceMetadata::new(
            WorkspaceId::new(),
            PathBuf::from("/test"),
            384,
        );

        assert_eq!(meta.document_count, 0);
        assert!(meta.is_compatible());

        // Record a document
        meta.record_document(
            PathBuf::from("test.rs"),
            DocumentId::new(),
            "hash123".to_string(),
            1000,
            Language::Rust,
            5,
        );

        assert_eq!(meta.document_count, 1);
        assert_eq!(meta.chunk_count, 5);
        assert_eq!(meta.total_bytes, 1000);

        // Check reindex need
        assert!(!meta.needs_reindex(&PathBuf::from("test.rs"), "hash123"));
        assert!(meta.needs_reindex(&PathBuf::from("test.rs"), "hash456"));
        assert!(meta.needs_reindex(&PathBuf::from("other.rs"), "hash123"));
    }
}
