//! Version control system for page changes with tar timestamp backups

use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;

/// Version control manager for page files
#[derive(Debug)]
pub struct VersionControl {
    project_root: PathBuf,
    save_dir: PathBuf,
}

/// Backup metadata
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackupInfo {
    pub timestamp: DateTime<Utc>,
    pub page_name: String,
    pub description: String,
    pub files_backed_up: Vec<String>,
    pub tar_file: String,
}

/// Command template for saving/loading page generation commands
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommandTemplate {
    pub name: String,
    pub description: String,
    pub timestamp: DateTime<Utc>,
    pub page_name: String,
    pub template: String,
    pub languages: Vec<String>,
    pub route_path: String,
    pub priority: f32,
    pub auto_scaffold: bool,
    pub replace_existing: bool,
    pub extra_flags: HashMap<String, String>,
}

impl VersionControl {
    /// Create new version control instance
    pub fn new(project_root: PathBuf) -> Result<Self> {
        let save_dir = Self::get_save_dir(&project_root)?;

        // Ensure save directory exists
        fs::create_dir_all(&save_dir)
            .with_context(|| format!("Failed to create save directory: {:?}", save_dir))?;

        Ok(Self {
            project_root,
            save_dir,
        })
    }

    /// Get save directory from environment or default
    fn get_save_dir(project_root: &Path) -> Result<PathBuf> {
        let default_path = project_root.join("page_manager").join("save");

        if let Ok(env_path) = std::env::var("PAGE_MANAGER_SAVE") {
            if env_path.starts_with('/') {
                // Absolute path
                Ok(PathBuf::from(env_path))
            } else {
                // Relative to project root
                Ok(project_root.join(env_path))
            }
        } else {
            Ok(default_path)
        }
    }

    /// Create backup of page files before making changes
    pub fn create_backup(
        &self,
        page_name: &str,
        files: &[PathBuf],
        description: Option<&str>,
    ) -> Result<BackupInfo> {
        let timestamp = Utc::now();
        let timestamp_str = timestamp.format("%Y%m%d_%H%M%S").to_string();
        let tar_filename = format!("{}_{}.tar.gz", page_name, timestamp_str);
        let tar_path = self.save_dir.join(&tar_filename);

        // Filter existing files
        let existing_files: Vec<&PathBuf> = files.iter().filter(|f| f.exists()).collect();

        if existing_files.is_empty() {
            anyhow::bail!("No existing files to backup for page: {}", page_name);
        }

        // Create tar archive
        self.create_tar_archive(&tar_path, &existing_files)?;

        let backup_info = BackupInfo {
            timestamp,
            page_name: page_name.to_string(),
            description: description.unwrap_or("Automatic backup").to_string(),
            files_backed_up: existing_files
                .iter()
                .map(|p| p.display().to_string())
                .collect(),
            tar_file: tar_filename,
        };

        // Save backup metadata
        self.save_backup_metadata(&backup_info)?;

        Ok(backup_info)
    }

    /// Create tar archive of files
    fn create_tar_archive(&self, tar_path: &Path, files: &[&PathBuf]) -> Result<()> {
        let mut cmd = Command::new("tar");
        cmd.arg("-czf")
            .arg(tar_path)
            .arg("-C")
            .arg(&self.project_root);

        // Add relative paths to tar
        for file in files {
            if let Ok(relative) = file.strip_prefix(&self.project_root) {
                cmd.arg(relative);
            }
        }

        let output = cmd
            .output()
            .with_context(|| "Failed to execute tar command")?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            anyhow::bail!("Tar command failed: {}", stderr);
        }

        Ok(())
    }

    /// Save backup metadata to JSON file
    fn save_backup_metadata(&self, backup_info: &BackupInfo) -> Result<()> {
        let metadata_file = self.save_dir.join(format!(
            "{}.json",
            backup_info.tar_file.replace(".tar.gz", "")
        ));
        let json = serde_json::to_string_pretty(backup_info)
            .with_context(|| "Failed to serialize backup metadata")?;

        fs::write(metadata_file, json).with_context(|| "Failed to write backup metadata")?;

        Ok(())
    }

    /// List all available backups for a page
    pub fn list_backups(&self, page_name: Option<&str>) -> Result<Vec<BackupInfo>> {
        let mut backups = Vec::new();

        if !self.save_dir.exists() {
            return Ok(backups);
        }

        let entries = fs::read_dir(&self.save_dir)
            .with_context(|| format!("Failed to read save directory: {:?}", self.save_dir))?;

        for entry in entries {
            let entry = entry?;
            let path = entry.path();

            if path.extension().map_or(false, |ext| ext == "json") {
                if let Ok(backup_info) = self.load_backup_metadata(&path) {
                    if page_name.is_none() || backup_info.page_name == page_name.unwrap() {
                        backups.push(backup_info);
                    }
                }
            }
        }

        // Sort by timestamp (newest first)
        backups.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));

        Ok(backups)
    }

    /// Load backup metadata from JSON file
    fn load_backup_metadata(&self, metadata_path: &Path) -> Result<BackupInfo> {
        let content = fs::read_to_string(metadata_path)
            .with_context(|| format!("Failed to read backup metadata: {:?}", metadata_path))?;

        let backup_info: BackupInfo =
            serde_json::from_str(&content).with_context(|| "Failed to parse backup metadata")?;

        Ok(backup_info)
    }

    /// Restore files from backup
    pub fn restore_backup(&self, backup_info: &BackupInfo) -> Result<()> {
        let tar_path = self.save_dir.join(&backup_info.tar_file);

        if !tar_path.exists() {
            anyhow::bail!("Backup tar file not found: {:?}", tar_path);
        }

        // Extract tar archive
        let output = Command::new("tar")
            .arg("-xzf")
            .arg(&tar_path)
            .arg("-C")
            .arg(&self.project_root)
            .output()
            .with_context(|| "Failed to execute tar extract command")?;

        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            anyhow::bail!("Tar extract failed: {}", stderr);
        }

        Ok(())
    }

    /// Clean up old backups (keep last N backups per page)
    pub fn cleanup_old_backups(&self, keep_count: usize) -> Result<()> {
        let mut page_backups: HashMap<String, Vec<BackupInfo>> = HashMap::new();

        // Group backups by page name
        for backup in self.list_backups(None)? {
            page_backups
                .entry(backup.page_name.clone())
                .or_insert_with(Vec::new)
                .push(backup);
        }

        // Remove old backups for each page
        for (page_name, mut backups) in page_backups {
            if backups.len() > keep_count {
                // Sort by timestamp (newest first)
                backups.sort_by(|a, b| b.timestamp.cmp(&a.timestamp));

                // Remove old backups
                for old_backup in backups.iter().skip(keep_count) {
                    let tar_path = self.save_dir.join(&old_backup.tar_file);
                    let json_path = self.save_dir.join(format!(
                        "{}.json",
                        old_backup.tar_file.replace(".tar.gz", "")
                    ));

                    // Remove files
                    if tar_path.exists() {
                        fs::remove_file(&tar_path).with_context(|| {
                            format!("Failed to remove old backup: {:?}", tar_path)
                        })?;
                    }

                    if json_path.exists() {
                        fs::remove_file(&json_path).with_context(|| {
                            format!("Failed to remove old metadata: {:?}", json_path)
                        })?;
                    }

                    println!(
                        "Cleaned up old backup for {}: {}",
                        page_name, old_backup.tar_file
                    );
                }
            }
        }

        Ok(())
    }
}

/// Command template manager
#[derive(Debug)]
pub struct CommandTemplateManager {
    _project_root: PathBuf,
    commands_dir: PathBuf,
}

impl CommandTemplateManager {
    /// Create new command template manager
    pub fn new(project_root: PathBuf) -> Result<Self> {
        let commands_dir = Self::get_commands_dir(&project_root)?;

        // Ensure commands directory exists
        fs::create_dir_all(&commands_dir)
            .with_context(|| format!("Failed to create commands directory: {:?}", commands_dir))?;

        Ok(Self {
            _project_root: project_root,
            commands_dir,
        })
    }

    /// Get commands directory from environment or default
    fn get_commands_dir(project_root: &Path) -> Result<PathBuf> {
        let default_path = project_root.join("page_manager").join("commands");

        if let Ok(env_path) = std::env::var("PAGE_MANAGER_CMDS") {
            if env_path.starts_with('/') {
                // Absolute path
                Ok(PathBuf::from(env_path))
            } else {
                // Relative to project root
                Ok(project_root.join(env_path))
            }
        } else {
            Ok(default_path)
        }
    }

    /// Save command template
    pub fn save_template(&self, template: &CommandTemplate) -> Result<()> {
        let filename = format!("{}.json", template.name.replace(' ', "_").to_lowercase());
        let file_path = self.commands_dir.join(filename);

        let json = serde_json::to_string_pretty(template)
            .with_context(|| "Failed to serialize command template")?;

        fs::write(file_path, json).with_context(|| "Failed to write command template")?;

        Ok(())
    }

    /// Load command template
    pub fn load_template(&self, template_name: &str) -> Result<CommandTemplate> {
        let filename = format!("{}.json", template_name.replace(' ', "_").to_lowercase());
        let file_path = self.commands_dir.join(filename);

        let content = fs::read_to_string(&file_path)
            .with_context(|| format!("Failed to read command template: {:?}", file_path))?;

        let template: CommandTemplate =
            serde_json::from_str(&content).with_context(|| "Failed to parse command template")?;

        Ok(template)
    }

    /// List all available command templates
    pub fn list_templates(&self) -> Result<Vec<String>> {
        let mut templates = Vec::new();

        if !self.commands_dir.exists() {
            return Ok(templates);
        }

        let entries = fs::read_dir(&self.commands_dir).with_context(|| {
            format!("Failed to read commands directory: {:?}", self.commands_dir)
        })?;

        for entry in entries {
            let entry = entry?;
            let path = entry.path();

            if path.extension().map_or(false, |ext| ext == "json") {
                if let Some(stem) = path.file_stem().and_then(|s| s.to_str()) {
                    templates.push(stem.to_string());
                }
            }
        }

        templates.sort();
        Ok(templates)
    }

    /// Delete command template
    pub fn delete_template(&self, template_name: &str) -> Result<()> {
        let filename = format!("{}.json", template_name.replace(' ', "_").to_lowercase());
        let file_path = self.commands_dir.join(filename);

        if file_path.exists() {
            fs::remove_file(&file_path)
                .with_context(|| format!("Failed to delete command template: {:?}", file_path))?;
        }

        Ok(())
    }

    /// Convert command template to rustelo-page command line
    pub fn template_to_command_line(&self, template: &CommandTemplate) -> String {
        let mut cmd = vec!["rustelo-page".to_string()];

        // Add page name
        cmd.push(template.page_name.clone());

        // Add template
        cmd.push("--template".to_string());
        cmd.push(template.template.clone());

        // Add languages
        if !template.languages.is_empty() {
            cmd.push("--languages".to_string());
            cmd.push(template.languages.join(","));
        }

        // Add route path
        if !template.route_path.is_empty() {
            cmd.push("--route".to_string());
            cmd.push(template.route_path.clone());
        }

        // Add priority
        if template.priority != 1.0 {
            cmd.push("--priority".to_string());
            cmd.push(template.priority.to_string());
        }

        // Add flags
        if template.auto_scaffold {
            cmd.push("--auto-scaffold".to_string());
        }

        if template.replace_existing {
            cmd.push("--replace-existing".to_string());
        }

        // Add extra flags
        for (flag, value) in &template.extra_flags {
            cmd.push(format!("--{}", flag));
            if !value.is_empty() {
                cmd.push(value.clone());
            }
        }

        cmd.join(" ")
    }
}
