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

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalQuery {
    pub doc_id: String,
    pub at_time: Option<DateTime<Utc>>,
    pub version_id: Option<String>,
    pub branch_name: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TemporalState {
    pub envelope: Envelope,
    pub reconstruction_info: ReconstructionInfo,
    pub version_context: VersionContext,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReconstructionInfo {
    pub is_exact_match: bool,
    pub reconstruction_path: Vec<String>, // version_ids used for reconstruction
    pub applied_deltas: usize,
    pub reconstruction_time_ms: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionContext {
    pub current_version: String,
    pub parent_versions: Vec<String>,
    pub child_versions: Vec<String>,
    pub branch_info: Option<BranchInfo>,
    pub merge_info: Option<MergeInfo>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BranchInfo {
    pub branch_name: String,
    pub is_main_branch: bool,
    pub branch_point: Option<String>, // version_id where this branch started
    pub merge_target: Option<String>, // target branch for merging
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MergeInfo {
    pub merged_from: Vec<String>, // branches that were merged
    pub merge_strategy: String,
    pub conflicts_resolved: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VersionDiff {
    pub from_version: String,
    pub to_version: String,
    pub changes: Vec<ContentChange>,
    pub metadata_changes: HashMap<String, MetadataChange>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContentChange {
    pub change_type: ChangeType,
    pub line_number: Option<usize>,
    pub old_content: Option<String>,
    pub new_content: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ChangeType {
    Addition,
    Deletion,
    Modification,
    Move,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MetadataChange {
    pub field: String,
    pub old_value: Option<serde_json::Value>,
    pub new_value: Option<serde_json::Value>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimelineView {
    pub doc_id: String,
    pub timeline: Vec<TimelineEntry>,
    pub branches: HashMap<String, BranchTimeline>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TimelineEntry {
    pub version_id: String,
    pub timestamp: DateTime<Utc>,
    pub author: Option<String>,
    pub message: Option<String>,
    pub change_summary: ChangeSummary,
    pub parents: Vec<String>,
    pub children: Vec<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ChangeSummary {
    pub lines_added: usize,
    pub lines_removed: usize,
    pub lines_modified: usize,
    pub files_changed: usize,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BranchTimeline {
    pub branch_name: String,
    pub entries: Vec<String>, // version_ids in chronological order
    pub branch_point: Option<String>,
    pub merge_points: Vec<String>,
}

pub struct TemporalEngine<S: EnvelopeStore> {
    store: S,
}

impl<S: EnvelopeStore> TemporalEngine<S> {
    pub fn new(store: S) -> Self {
        TemporalEngine { store }
    }
    
    pub async fn get_state_at_time(&self, query: TemporalQuery) -> Result<TemporalState> {
        let start_time = std::time::Instant::now();
        
        let history = self.store.get_history(&query.doc_id).await?;
        
        let target_version = if let Some(version_id) = query.version_id {
            version_id
        } else if let Some(at_time) = query.at_time {
            self.find_version_at_time(&history, at_time)?
        } else {
            // Get latest version
            history.versions
                .iter()
                .max_by_key(|v| v.timestamp)
                .ok_or_else(|| UCXLError::EnvelopeNotFound(query.doc_id.clone()))?
                .version_id.clone()
        };
        
        let envelope = self.reconstruct_state(&query.doc_id, &target_version).await?;
        
        let reconstruction_time = start_time.elapsed().as_millis() as u64;
        
        let reconstruction_info = ReconstructionInfo {
            is_exact_match: true, // For now, assuming exact match
            reconstruction_path: vec![target_version.clone()],
            applied_deltas: 0,
            reconstruction_time_ms: reconstruction_time,
        };
        
        let version_context = self.build_version_context(&history, &target_version)?;
        
        Ok(TemporalState {
            envelope,
            reconstruction_info,
            version_context,
        })
    }
    
    pub async fn get_timeline(&self, doc_id: &str) -> Result<TimelineView> {
        let history = self.store.get_history(doc_id).await?;
        
        let mut timeline = Vec::new();
        let mut version_graph: HashMap<String, (Vec<String>, Vec<String>)> = HashMap::new();
        
        // Build parent-child relationships
        for version in &history.versions {
            let parents = version.parent_versions.clone();
            let children = Vec::new();
            
            version_graph.insert(version.version_id.clone(), (parents.clone(), children));
            
            // Update children for parent versions
            for parent in &parents {
                if let Some((_, parent_children)) = version_graph.get_mut(parent) {
                    parent_children.push(version.version_id.clone());
                }
            }
        }
        
        // Create timeline entries
        for version in &history.versions {
            let (parents, children) = version_graph.get(&version.version_id)
                .cloned()
                .unwrap_or((Vec::new(), Vec::new()));
            
            let change_summary = self.calculate_change_summary(&version.version_id).await?;
            
            timeline.push(TimelineEntry {
                version_id: version.version_id.clone(),
                timestamp: version.timestamp,
                author: version.author.clone(),
                message: version.commit_message.clone(),
                change_summary,
                parents,
                children,
            });
        }
        
        // Sort by timestamp
        timeline.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
        
        let branches = self.extract_branches(&history);
        
        Ok(TimelineView {
            doc_id: doc_id.to_string(),
            timeline,
            branches,
        })
    }
    
    pub async fn diff_versions(&self, _doc_id: &str, from_version: &str, to_version: &str) -> Result<VersionDiff> {
        // For now, just retrieve by envelope ID directly
        let from_envelope = self.store.retrieve(from_version).await?
            .ok_or_else(|| UCXLError::EnvelopeNotFound(from_version.to_string()))?;
            
        let to_envelope = self.store.retrieve(to_version).await?
            .ok_or_else(|| UCXLError::EnvelopeNotFound(to_version.to_string()))?;
        
        let changes = self.compute_content_diff(&from_envelope.content.raw, &to_envelope.content.raw);
        let metadata_changes = self.compute_metadata_diff(&from_envelope.metadata, &to_envelope.metadata);
        
        Ok(VersionDiff {
            from_version: from_version.to_string(),
            to_version: to_version.to_string(),
            changes,
            metadata_changes,
        })
    }
    
    async fn reconstruct_state(&self, doc_id: &str, version_id: &str) -> Result<Envelope> {
        // For now, just retrieve the exact version
        // TODO: Implement delta-based reconstruction for efficiency
        
        // First try to retrieve by version_id directly (it might be an envelope_id)
        if let Ok(Some(envelope)) = self.store.retrieve(version_id).await {
            return Ok(envelope);
        }
        
        // Then try to retrieve by doc_id and version
        self.store.retrieve_version(doc_id, version_id).await?
            .ok_or_else(|| UCXLError::EnvelopeNotFound(version_id.to_string()))
    }
    
    fn find_version_at_time(&self, history: &EnvelopeHistory, at_time: DateTime<Utc>) -> Result<String> {
        history.versions
            .iter()
            .filter(|v| v.timestamp <= at_time)
            .max_by_key(|v| v.timestamp)
            .map(|v| v.version_id.clone())
            .ok_or_else(|| UCXLError::TemporalError(format!("No version found at or before {}", at_time)))
    }
    
    fn build_version_context(&self, history: &EnvelopeHistory, version_id: &str) -> Result<VersionContext> {
        let version = history.versions
            .iter()
            .find(|v| v.version_id == version_id)
            .ok_or_else(|| UCXLError::EnvelopeNotFound(version_id.to_string()))?;
        
        // Find children (versions that have this as parent)
        let child_versions: Vec<_> = history.versions
            .iter()
            .filter(|v| v.parent_versions.contains(&version_id.to_string()))
            .map(|v| v.version_id.clone())
            .collect();
        
        // TODO: Implement proper branch detection
        let branch_info = Some(BranchInfo {
            branch_name: "main".to_string(),
            is_main_branch: true,
            branch_point: None,
            merge_target: None,
        });
        
        Ok(VersionContext {
            current_version: version_id.to_string(),
            parent_versions: version.parent_versions.clone(),
            child_versions,
            branch_info,
            merge_info: None, // TODO: Detect merges
        })
    }
    
    async fn calculate_change_summary(&self, _version_id: &str) -> Result<ChangeSummary> {
        // TODO: Implement actual change calculation
        Ok(ChangeSummary {
            lines_added: 0,
            lines_removed: 0,
            lines_modified: 0,
            files_changed: 1,
        })
    }
    
    fn extract_branches(&self, history: &EnvelopeHistory) -> HashMap<String, BranchTimeline> {
        let mut branches = HashMap::new();
        
        // For now, just create a main branch with all versions
        let main_branch = BranchTimeline {
            branch_name: "main".to_string(),
            entries: history.versions.iter().map(|v| v.version_id.clone()).collect(),
            branch_point: None,
            merge_points: Vec::new(),
        };
        
        branches.insert("main".to_string(), main_branch);
        branches
    }
    
    fn compute_content_diff(&self, from_content: &str, to_content: &str) -> Vec<ContentChange> {
        // Simple line-by-line diff implementation
        let from_lines: Vec<&str> = from_content.lines().collect();
        let to_lines: Vec<&str> = to_content.lines().collect();
        
        let mut changes = Vec::new();
        let max_len = from_lines.len().max(to_lines.len());
        
        for i in 0..max_len {
            let from_line = from_lines.get(i);
            let to_line = to_lines.get(i);
            
            match (from_line, to_line) {
                (Some(from), Some(to)) => {
                    if from != to {
                        changes.push(ContentChange {
                            change_type: ChangeType::Modification,
                            line_number: Some(i + 1),
                            old_content: Some(from.to_string()),
                            new_content: Some(to.to_string()),
                        });
                    }
                }
                (Some(from), None) => {
                    changes.push(ContentChange {
                        change_type: ChangeType::Deletion,
                        line_number: Some(i + 1),
                        old_content: Some(from.to_string()),
                        new_content: None,
                    });
                }
                (None, Some(to)) => {
                    changes.push(ContentChange {
                        change_type: ChangeType::Addition,
                        line_number: Some(i + 1),
                        old_content: None,
                        new_content: Some(to.to_string()),
                    });
                }
                (None, None) => unreachable!(),
            }
        }
        
        changes
    }
    
    fn compute_metadata_diff(
        &self,
        from_metadata: &crate::envelope::EnvelopeMetadata,
        to_metadata: &crate::envelope::EnvelopeMetadata,
    ) -> HashMap<String, MetadataChange> {
        let mut changes = HashMap::new();
        
        // Compare author
        if from_metadata.author != to_metadata.author {
            changes.insert("author".to_string(), MetadataChange {
                field: "author".to_string(),
                old_value: from_metadata.author.as_ref().map(|s| serde_json::Value::String(s.clone())),
                new_value: to_metadata.author.as_ref().map(|s| serde_json::Value::String(s.clone())),
            });
        }
        
        // Compare title
        if from_metadata.title != to_metadata.title {
            changes.insert("title".to_string(), MetadataChange {
                field: "title".to_string(),
                old_value: from_metadata.title.as_ref().map(|s| serde_json::Value::String(s.clone())),
                new_value: to_metadata.title.as_ref().map(|s| serde_json::Value::String(s.clone())),
            });
        }
        
        // Compare tags
        if from_metadata.tags != to_metadata.tags {
            changes.insert("tags".to_string(), MetadataChange {
                field: "tags".to_string(),
                old_value: Some(serde_json::to_value(&from_metadata.tags).unwrap()),
                new_value: Some(serde_json::to_value(&to_metadata.tags).unwrap()),
            });
        }
        
        changes
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::{InMemoryEnvelopeStore, UCXLUri, envelope::EnvelopeMetadata};
    use std::collections::HashMap;
    
    #[tokio::test]
    async fn test_temporal_engine_basic_functionality() {
        let store = InMemoryEnvelopeStore::new();
        let engine = TemporalEngine::new(store);
        
        // Create and store a test envelope
        let uri = UCXLUri::new("ucxl://example.com/test-doc").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 = crate::Envelope::new(
            uri,
            "# Initial Content".to_string(),
            "text/markdown".to_string(),
            metadata,
        ).unwrap();
        
        engine.store.store(&envelope).await.unwrap();
        
        // Test getting state by version
        let query = TemporalQuery {
            doc_id: "/test-doc".to_string(),
            at_time: None,
            version_id: Some(envelope.id.clone()), // Use envelope.id instead of version
            branch_name: None,
        };
        
        let state = engine.get_state_at_time(query).await.unwrap();
        assert_eq!(state.envelope.id, envelope.id);
        assert!(state.reconstruction_info.is_exact_match);
    }
    
    #[tokio::test]
    async fn test_timeline_generation() {
        let store = InMemoryEnvelopeStore::new();
        let engine = TemporalEngine::new(store);
        
        // Create multiple versions
        for i in 0..3 {
            let uri = UCXLUri::new("ucxl://example.com/timeline-test").unwrap();
            let metadata = EnvelopeMetadata {
                author: Some(format!("author_{}", i)),
                title: Some(format!("Version {}", i)),
                tags: vec!["timeline".to_string()],
                source: None,
                context_data: HashMap::new(),
            };
            
            let envelope = crate::Envelope::new(
                uri,
                format!("# Content Version {}", i),
                "text/markdown".to_string(),
                metadata,
            ).unwrap();
            
            engine.store.store(&envelope).await.unwrap();
        }
        
        let timeline = engine.get_timeline("/timeline-test").await.unwrap();
        assert_eq!(timeline.timeline.len(), 3);
        assert!(timeline.branches.contains_key("main"));
    }
    
    #[tokio::test]
    async fn test_version_diff() {
        let store = InMemoryEnvelopeStore::new();
        let engine = TemporalEngine::new(store);
        let uri = UCXLUri::new("ucxl://example.com/diff-test").unwrap();
        
        // Create two versions
        let metadata1 = EnvelopeMetadata {
            author: Some("author".to_string()),
            title: Some("Version 1".to_string()),
            tags: vec!["diff".to_string()],
            source: None,
            context_data: HashMap::new(),
        };
        
        let envelope1 = crate::Envelope::new(
            uri.clone(),
            "# Original Content\nLine 1\nLine 2".to_string(),
            "text/markdown".to_string(),
            metadata1,
        ).unwrap();
        
        let metadata2 = EnvelopeMetadata {
            author: Some("author".to_string()),
            title: Some("Version 2".to_string()),
            tags: vec!["diff".to_string()],
            source: None,
            context_data: HashMap::new(),
        };
        
        let envelope2 = crate::Envelope::new(
            uri,
            "# Modified Content\nLine 1\nLine 2 Modified\nNew Line 3".to_string(),
            "text/markdown".to_string(),
            metadata2,
        ).unwrap();
        
        engine.store.store(&envelope1).await.unwrap();
        engine.store.store(&envelope2).await.unwrap();
        
        let diff = engine.diff_versions(
            "/diff-test",
            &envelope1.id, // Use envelope.id instead of version
            &envelope2.id,
        ).await.unwrap();
        
        assert!(!diff.changes.is_empty());
        assert!(diff.metadata_changes.contains_key("title"));
    }
}