//! File-based Version Control Backend
//!
//! Implements version control using the filesystem for storage

use std::collections::hash_map::DefaultHasher;
// use std::collections::HashMap; // Commented out as unused
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};

use anyhow::{Context, Result};
use async_trait::async_trait;

use super::models::{
    ChangeType, ContentVersion, DiffChange, DiffType, VersionDiff, VersionHistory,
};
use super::traits::{BackendInfo, VersionControlBackend};
use crate::content::file_loader::{FileContent, FrontMatter};

/// File-based implementation of version control
pub struct FileVersionControlBackend {
    content_root: PathBuf,
    version_storage_path: PathBuf,
}

impl FileVersionControlBackend {
    /// Create a new file-based version control backend
    pub fn new<P: AsRef<Path>>(content_root: P) -> Self {
        let content_root = content_root.as_ref().to_path_buf();
        let version_storage_path = content_root.join(".versions");

        Self {
            content_root,
            version_storage_path,
        }
    }

    /// Generate a new semantic version string
    fn generate_version_string(&self, content_path: &str) -> Result<String> {
        // Try to get existing history to determine next version
        let history_result = tokio::task::block_in_place(|| {
            tokio::runtime::Handle::current().block_on(self.get_version_history(content_path))
        });

        let next_version = match history_result {
            Ok(history) if !history.versions.is_empty() => {
                // Parse the current version and increment patch number
                let current = &history.current_version;
                let parts: Vec<&str> = current.split('.').collect();
                if parts.len() == 3 {
                    let major: i32 = parts[0].parse().unwrap_or(1);
                    let minor: i32 = parts[1].parse().unwrap_or(0);
                    let patch: i32 = parts[2].parse().unwrap_or(0);
                    format!("{}.{}.{}", major, minor, patch + 1)
                } else {
                    "1.0.0".to_string()
                }
            }
            _ => "1.0.0".to_string(),
        };

        Ok(next_version)
    }

    /// Calculate content checksum
    fn calculate_checksum(&self, content: &str) -> String {
        let mut hasher = DefaultHasher::new();
        content.hash(&mut hasher);
        format!("{:x}", hasher.finish())
    }

    /// Store version metadata
    async fn store_version_metadata(
        &self,
        content_path: &str,
        version: &ContentVersion,
    ) -> Result<()> {
        let metadata_dir = self.version_storage_path.join("metadata");
        tokio::fs::create_dir_all(&metadata_dir).await?;

        let metadata_file =
            metadata_dir.join(format!("{}.json", content_path.replace(['/', '\\'], "_")));

        let mut versions = self
            .load_version_metadata(content_path)
            .await
            .unwrap_or_default();
        versions.push(version.clone());

        let json_content = serde_json::to_string_pretty(&versions)?;
        tokio::fs::write(metadata_file, json_content).await?;

        Ok(())
    }

    /// Load version metadata
    async fn load_version_metadata(&self, content_path: &str) -> Result<Vec<ContentVersion>> {
        let metadata_file = self
            .version_storage_path
            .join("metadata")
            .join(format!("{}.json", content_path.replace(['/', '\\'], "_")));

        if !metadata_file.exists() {
            return Ok(Vec::new());
        }

        let json_content = tokio::fs::read_to_string(metadata_file).await?;
        let versions: Vec<ContentVersion> = serde_json::from_str(&json_content)?;
        Ok(versions)
    }

    /// Store content snapshot
    async fn store_content_snapshot(
        &self,
        content_path: &str,
        version: &str,
        content: &FileContent,
    ) -> Result<()> {
        let snapshot_path = self.get_version_snapshot_path(content_path, version);

        if let Some(parent) = snapshot_path.parent() {
            tokio::fs::create_dir_all(parent).await?;
        }

        // Store the full content with frontmatter
        let full_content = self.serialize_file_content(content)?;
        tokio::fs::write(snapshot_path, full_content).await?;

        Ok(())
    }

    /// Get path for version snapshot
    fn get_version_snapshot_path(&self, content_path: &str, version: &str) -> PathBuf {
        let safe_path = content_path.replace(['/', '\\'], "_");
        self.version_storage_path
            .join("snapshots")
            .join(format!("{}_{}.md", safe_path, version))
    }

    /// Serialize file content with frontmatter
    fn serialize_file_content(&self, content: &FileContent) -> Result<String> {
        let frontmatter_yaml = serde_yaml::to_string(&content.front_matter)?;
        Ok(format!("---\n{}---\n{}", frontmatter_yaml, content.content))
    }

    /// Parse stored content
    async fn parse_stored_content(&self, content: &str, file_path: &Path) -> Result<FileContent> {
        let file_name = file_path
            .file_name()
            .and_then(|n| n.to_str())
            .unwrap_or("unknown")
            .to_string();

        if content.starts_with("---") {
            let parts: Vec<&str> = content.splitn(3, "---").collect();
            if parts.len() < 3 {
                return Err(anyhow::anyhow!("Invalid stored content format"));
            }

            let front_matter_str = parts[1].trim();
            let content_str = parts[2].trim();

            let front_matter: FrontMatter = serde_yaml::from_str(front_matter_str)
                .context("Failed to parse stored frontmatter")?;

            Ok(FileContent {
                front_matter,
                content: content_str.to_string(),
                file_path: file_path.to_path_buf(),
                file_name,
            })
        } else {
            Err(anyhow::anyhow!("Stored content missing frontmatter"))
        }
    }

    /// Write content to file (for rollback operations)
    async fn write_content_to_file(&self, file_path: &Path, content: &FileContent) -> Result<()> {
        let full_content = self.serialize_file_content(content)?;
        tokio::fs::write(file_path, full_content).await?;
        Ok(())
    }

    /// Generate line-by-line diff between two contents
    fn generate_line_diff(&self, from_content: &str, to_content: &str) -> Vec<DiffChange> {
        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_lines = from_lines.len().max(to_lines.len());

        for i in 0..max_lines {
            match (from_lines.get(i), to_lines.get(i)) {
                (Some(from_line), Some(to_line)) => {
                    if from_line != to_line {
                        changes.push(DiffChange {
                            change_type: ChangeType::Modification,
                            line_number: Some(i + 1),
                            content: format!("- {}\n+ {}", from_line, to_line),
                            context: None,
                        });
                    }
                }
                (Some(from_line), None) => {
                    changes.push(DiffChange {
                        change_type: ChangeType::Deletion,
                        line_number: Some(i + 1),
                        content: format!("- {}", from_line),
                        context: None,
                    });
                }
                (None, Some(to_line)) => {
                    changes.push(DiffChange {
                        change_type: ChangeType::Addition,
                        line_number: Some(i + 1),
                        content: format!("+ {}", to_line),
                        context: None,
                    });
                }
                (None, None) => break,
            }
        }

        changes
    }
}

#[async_trait]
impl VersionControlBackend for FileVersionControlBackend {
    async fn create_version(
        &self,
        content_path: &str,
        content: &FileContent,
        author: Option<String>,
        version_notes: Option<String>,
    ) -> Result<String> {
        // Generate version string
        let version = self.generate_version_string(content_path)?;

        // Calculate content checksum
        let checksum = self.calculate_checksum(&content.content);

        // Get content size
        let size_bytes = content.content.len() as u64;

        // Get parent version
        let parent_version = content.front_matter.version.clone();

        // Create version record
        let content_version = ContentVersion::new(
            version.clone(),
            content_path.to_string(),
            author,
            version_notes,
            parent_version,
            checksum,
            size_bytes,
        );

        // Store version metadata
        self.store_version_metadata(content_path, &content_version)
            .await?;

        // Store content snapshot
        self.store_content_snapshot(content_path, &version, content)
            .await?;

        tracing::info!("Created version {} for content: {}", version, content_path);
        Ok(version)
    }

    async fn get_version_history(&self, content_path: &str) -> Result<VersionHistory> {
        let versions = self
            .load_version_metadata(content_path)
            .await
            .unwrap_or_default();

        let current_version = versions
            .iter()
            .max_by_key(|v| &v.created_at)
            .map(|v| v.version.clone())
            .unwrap_or_else(|| "1.0.0".to_string());

        let mut history = VersionHistory::new(content_path.to_string());
        history.current_version = current_version;
        history.total_versions = versions.len();

        for version in versions {
            history.add_version(version);
        }

        Ok(history)
    }

    async fn get_version(&self, content_path: &str, version: &str) -> Result<Option<FileContent>> {
        let snapshot_path = self.get_version_snapshot_path(content_path, version);

        if !snapshot_path.exists() {
            return Ok(None);
        }

        let content = tokio::fs::read_to_string(&snapshot_path)
            .await
            .context("Failed to read version snapshot")?;

        let file_content = self.parse_stored_content(&content, &snapshot_path).await?;
        Ok(Some(file_content))
    }

    async fn rollback_to_version(
        &self,
        content_path: &str,
        target_version: &str,
        author: Option<String>,
    ) -> Result<String> {
        // Get the target version content
        let version_content = self
            .get_version(content_path, target_version)
            .await?
            .context("Target version not found")?;

        // Create a new version based on the rollback
        let rollback_notes = format!("Rollback to version {}", target_version);
        let new_version = self
            .create_version(content_path, &version_content, author, Some(rollback_notes))
            .await?;

        // Update the actual file
        let actual_file_path = self.content_root.join(content_path);
        self.write_content_to_file(&actual_file_path, &version_content)
            .await?;

        tracing::info!(
            "Rolled back {} to version {} (new version: {})",
            content_path,
            target_version,
            new_version
        );
        Ok(new_version)
    }

    async fn generate_diff(
        &self,
        content_path: &str,
        from_version: &str,
        to_version: &str,
    ) -> Result<VersionDiff> {
        let from_content = self
            .get_version(content_path, from_version)
            .await?
            .context("From version not found")?;
        let to_content = self
            .get_version(content_path, to_version)
            .await?
            .context("To version not found")?;

        let changes = self.generate_line_diff(&from_content.content, &to_content.content);

        let mut _added = 0;
        let mut _removed = 0;
        let mut _modified = 0;

        for change in &changes {
            match change.change_type {
                ChangeType::Addition => _added += 1,
                ChangeType::Deletion => _removed += 1,
                ChangeType::Modification => _modified += 1,
                ChangeType::Context => {}
            }
        }

        let mut diff = VersionDiff::new(
            from_version.to_string(),
            to_version.to_string(),
            DiffType::LineBased,
        );

        for change in changes {
            diff.add_change(change);
        }

        Ok(diff)
    }

    async fn list_versions(&self, content_path: &str) -> Result<Vec<String>> {
        let versions = self.load_version_metadata(content_path).await?;
        let mut version_strings: Vec<String> = versions.into_iter().map(|v| v.version).collect();

        version_strings.sort();
        Ok(version_strings)
    }

    async fn auto_version_content(
        &self,
        content_path: &str,
        content: &FileContent,
        author: Option<String>,
    ) -> Result<Option<String>> {
        // Check if content has changed since last version
        if self.has_content_changed(content_path, content).await? {
            let version = self
                .create_version(
                    content_path,
                    content,
                    author,
                    Some("Auto-versioned on save".to_string()),
                )
                .await?;

            return Ok(Some(version));
        }

        Ok(None)
    }

    async fn has_content_changed(&self, content_path: &str, content: &FileContent) -> Result<bool> {
        let history = self.get_version_history(content_path).await?;

        if let Some(latest_version) = history.get_latest_version() {
            let current_checksum = self.calculate_checksum(&content.content);
            Ok(current_checksum != latest_version.checksum)
        } else {
            // No versions exist, so this is a change
            Ok(true)
        }
    }

    async fn get_backend_info(&self) -> Result<BackendInfo> {
        // Count total versions and content items
        let mut total_versions = 0;
        let mut total_content_items = 0;

        let metadata_dir = self.version_storage_path.join("metadata");
        if metadata_dir.exists() {
            let mut entries = tokio::fs::read_dir(&metadata_dir).await?;
            while let Some(entry) = entries.next_entry().await? {
                if entry.file_type().await?.is_file() {
                    total_content_items += 1;
                    // Count versions in this file
                    if let Ok(versions) = self
                        .load_version_metadata(
                            &entry
                                .file_name()
                                .to_string_lossy()
                                .replace("_", "/")
                                .replace(".json", ""),
                        )
                        .await
                    {
                        total_versions += versions.len();
                    }
                }
            }
        }

        Ok(BackendInfo {
            backend_type: "file-based".to_string(),
            supports_branching: false,
            supports_merging: false,
            storage_location: self.version_storage_path.to_string_lossy().to_string(),
            total_versions,
            total_content_items,
        })
    }
}
