use serde::{Serialize, Deserialize};
use chrono::{DateTime, Utc};
use std::collections::HashMap;
use crate::{UCXLUri, Result, UCXLError};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Envelope {
    pub id: String,
    pub ucxl_uri: UCXLUri,
    pub content: EnvelopeContent,
    pub metadata: EnvelopeMetadata,
    pub version: String,
    pub parent_version: Option<String>,
    pub timestamp: DateTime<Utc>,
    pub content_hash: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EnvelopeContent {
    pub raw: String,
    pub content_type: String,
    pub encoding: String,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EnvelopeMetadata {
    pub author: Option<String>,
    pub title: Option<String>,
    pub tags: Vec<String>,
    pub source: Option<String>,
    pub context_data: HashMap<String, serde_json::Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct MarkdownContext {
    pub doc_id: String,
    pub markdown_content: String,
    pub front_matter: Option<HashMap<String, serde_json::Value>>,
    pub metadata: ContextMetadata,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ContextMetadata {
    pub author: Option<String>,
    pub title: Option<String>,
    pub created_at: DateTime<Utc>,
    pub modified_at: DateTime<Utc>,
    pub tags: Vec<String>,
    pub parent_version: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct EnvelopeHistory {
    pub doc_id: String,
    pub versions: Vec<VersionInfo>,
    pub branches: HashMap<String, Vec<String>>, // branch_name -> version_ids
}

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct VersionInfo {
    pub version_id: String,
    pub timestamp: DateTime<Utc>,
    pub parent_versions: Vec<String>,
    pub author: Option<String>,
    pub commit_message: Option<String>,
    pub content_hash: String,
}

impl Envelope {
    pub fn new(
        ucxl_uri: UCXLUri,
        content: String,
        content_type: String,
        metadata: EnvelopeMetadata,
    ) -> Result<Self> {
        let id = ucxl_uri.to_content_hash();
        let timestamp = Utc::now();
        let version = uuid::Uuid::new_v4().to_string();
        
        let content = EnvelopeContent {
            raw: content,
            content_type,
            encoding: "utf-8".to_string(),
        };
        
        let content_hash = Self::compute_content_hash(&content)?;
        
        Ok(Envelope {
            id,
            ucxl_uri,
            content,
            metadata,
            version,
            parent_version: None,
            timestamp,
            content_hash,
        })
    }
    
    pub fn with_parent(mut self, parent_version: String) -> Self {
        self.parent_version = Some(parent_version);
        self
    }
    
    fn compute_content_hash(content: &EnvelopeContent) -> Result<String> {
        use sha2::{Digest, Sha256};
        
        let serialized = serde_json::to_string(content)
            .map_err(|e| UCXLError::SerializationError(e.to_string()))?;
        
        let mut hasher = Sha256::new();
        hasher.update(serialized.as_bytes());
        Ok(hex::encode(hasher.finalize()))
    }
    
    pub fn is_markdown(&self) -> bool {
        self.content.content_type == "text/markdown"
    }
    
    pub fn get_content_size(&self) -> usize {
        self.content.raw.len()
    }
}

impl MarkdownContext {
    pub fn new(
        doc_id: String,
        markdown_content: String,
        author: Option<String>,
        title: Option<String>,
    ) -> Self {
        let now = Utc::now();
        
        MarkdownContext {
            doc_id,
            markdown_content,
            front_matter: None,
            metadata: ContextMetadata {
                author,
                title,
                created_at: now,
                modified_at: now,
                tags: Vec::new(),
                parent_version: None,
            },
        }
    }
    
    pub fn with_front_matter(mut self, front_matter: HashMap<String, serde_json::Value>) -> Self {
        self.front_matter = Some(front_matter);
        self
    }
    
    pub fn with_tags(mut self, tags: Vec<String>) -> Self {
        self.metadata.tags = tags;
        self
    }
    
    pub fn to_envelope(&self, ucxl_uri: UCXLUri) -> Result<Envelope> {
        let envelope_metadata = EnvelopeMetadata {
            author: self.metadata.author.clone(),
            title: self.metadata.title.clone(),
            tags: self.metadata.tags.clone(),
            source: None,
            context_data: self.front_matter.clone().unwrap_or_default(),
        };
        
        Envelope::new(
            ucxl_uri,
            self.markdown_content.clone(),
            "text/markdown".to_string(),
            envelope_metadata,
        )
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::UCXLUri;
    
    #[test]
    fn test_envelope_creation() {
        let uri = UCXLUri::new("ucxl://example.com/doc1").unwrap();
        let metadata = EnvelopeMetadata {
            author: Some("test_author".to_string()),
            title: Some("Test Document".to_string()),
            tags: vec!["test".to_string()],
            source: None,
            context_data: HashMap::new(),
        };
        
        let envelope = Envelope::new(
            uri,
            "# Test Content".to_string(),
            "text/markdown".to_string(),
            metadata,
        ).unwrap();
        
        assert!(!envelope.id.is_empty());
        assert!(!envelope.content_hash.is_empty());
        assert!(envelope.is_markdown());
        assert_eq!(envelope.get_content_size(), 14);
    }
    
    #[test]
    fn test_markdown_context_to_envelope() {
        let context = MarkdownContext::new(
            "doc1".to_string(),
            "# Test Markdown".to_string(),
            Some("author".to_string()),
            Some("Test Title".to_string()),
        );
        
        let uri = UCXLUri::new("ucxl://example.com/doc1").unwrap();
        let envelope = context.to_envelope(uri).unwrap();
        
        assert!(envelope.is_markdown());
        assert_eq!(envelope.metadata.author, Some("author".to_string()));
        assert_eq!(envelope.metadata.title, Some("Test Title".to_string()));
    }
}