//! File processors for different asset types

use std::collections::HashMap;
use std::path::{Path, PathBuf};

use async_trait::async_trait;

use crate::assets::AssetMerger;
use crate::assets::{FileProcessor, ProcessedFile, ProcessingContext};
use crate::error::{Result, RusteloError};

/// Script file processor for shell scripts, JavaScript, etc.
pub struct ScriptFileProcessor;

#[async_trait]
impl FileProcessor for ScriptFileProcessor {
    fn handles_extensions(&self) -> Vec<&'static str> {
        vec![
            "sh", "bash", "zsh", "fish", "js", "mjs", "ts", "py", "rb", "pl", "php",
        ]
    }

    async fn process_file(
        &self,
        source_path: &Path,
        target_path: &Path,
        context: &ProcessingContext,
    ) -> Result<ProcessedFile> {
        // Create target directory
        if let Some(parent) = target_path.parent() {
            std::fs::create_dir_all(parent).map_err(RusteloError::Io)?;
        }

        // Read source content
        let content = std::fs::read_to_string(source_path).map_err(RusteloError::Io)?;

        // Apply template substitution
        let processed_content = self.substitute_variables(&content, &context.variables)?;

        // Write processed content
        std::fs::write(target_path, processed_content).map_err(RusteloError::Io)?;

        // Make executable for shell scripts
        if self.is_executable_script(source_path) {
            self.make_executable(target_path)?;
        }

        Ok(ProcessedFile {
            target_path: target_path.to_path_buf(),
            source_path: source_path.to_path_buf(),
            merged: false,
            processor: "script".to_string(),
        })
    }
}

impl ScriptFileProcessor {
    fn substitute_variables(
        &self,
        content: &str,
        variables: &HashMap<String, String>,
    ) -> Result<String> {
        let mut result = content.to_string();

        // Simple template substitution using {{variable}} syntax
        for (key, value) in variables {
            let pattern = format!("{{{{{}}}}}", key);
            result = result.replace(&pattern, value);
        }

        Ok(result)
    }

    fn is_executable_script(&self, path: &Path) -> bool {
        matches!(
            path.extension().and_then(|ext| ext.to_str()),
            Some("sh")
                | Some("bash")
                | Some("zsh")
                | Some("fish")
                | Some("py")
                | Some("rb")
                | Some("pl")
        )
    }

    #[cfg(unix)]
    fn make_executable(&self, path: &Path) -> Result<()> {
        use std::os::unix::fs::PermissionsExt;

        let metadata = std::fs::metadata(path).map_err(RusteloError::Io)?;
        let mut permissions = metadata.permissions();
        permissions.set_mode(permissions.mode() | 0o755);

        std::fs::set_permissions(path, permissions).map_err(RusteloError::Io)?;

        Ok(())
    }

    #[cfg(not(unix))]
    fn make_executable(&self, _path: &Path) -> Result<()> {
        // On non-Unix systems, we don't need to set execute permissions
        Ok(())
    }
}

/// Configuration file processor with intelligent merging
pub struct ConfigFileProcessor;

#[async_trait]
impl FileProcessor for ConfigFileProcessor {
    fn handles_extensions(&self) -> Vec<&'static str> {
        vec![
            "json",
            "toml",
            "yaml",
            "yml",
            "ini",
            "conf",
            "config",
            "env",
            "properties",
            "xml",
            "cfg",
        ]
    }

    fn can_merge(&self) -> bool {
        true
    }

    async fn process_file(
        &self,
        source_path: &Path,
        target_path: &Path,
        context: &ProcessingContext,
    ) -> Result<ProcessedFile> {
        // Create target directory
        if let Some(parent) = target_path.parent() {
            std::fs::create_dir_all(parent).map_err(RusteloError::Io)?;
        }

        // Read and process content
        let content = std::fs::read_to_string(source_path).map_err(RusteloError::Io)?;

        let processed_content = self.substitute_variables(&content, &context.variables)?;

        // Write processed content
        std::fs::write(target_path, processed_content).map_err(RusteloError::Io)?;

        Ok(ProcessedFile {
            target_path: target_path.to_path_buf(),
            source_path: source_path.to_path_buf(),
            merged: false,
            processor: "config".to_string(),
        })
    }

    async fn merge_files(
        &self,
        files: &[PathBuf],
        target_path: &Path,
        context: &ProcessingContext,
    ) -> Result<ProcessedFile> {
        if files.is_empty() {
            return Err(RusteloError::generic("Cannot merge empty file list"));
        }

        // Create target directory
        if let Some(parent) = target_path.parent() {
            std::fs::create_dir_all(parent).map_err(RusteloError::Io)?;
        }

        let extension = target_path
            .extension()
            .and_then(|ext| ext.to_str())
            .unwrap_or("");

        match extension {
            "json" => self.merge_json_files(files, target_path, context).await,
            "toml" => self.merge_toml_files(files, target_path, context).await,
            _ => {
                // For other config types, use priority override
                let content = std::fs::read_to_string(&files[0]).map_err(RusteloError::Io)?;
                let processed = self.substitute_variables(&content, &context.variables)?;
                std::fs::write(target_path, processed).map_err(RusteloError::Io)?;

                Ok(ProcessedFile {
                    target_path: target_path.to_path_buf(),
                    source_path: files[0].clone(),
                    merged: false,
                    processor: "config".to_string(),
                })
            }
        }
    }
}

impl ConfigFileProcessor {
    fn substitute_variables(
        &self,
        content: &str,
        variables: &HashMap<String, String>,
    ) -> Result<String> {
        let mut result = content.to_string();

        for (key, value) in variables {
            let pattern = format!("{{{{{}}}}}", key);
            result = result.replace(&pattern, value);
        }

        Ok(result)
    }

    async fn merge_json_files(
        &self,
        files: &[PathBuf],
        target_path: &Path,
        context: &ProcessingContext,
    ) -> Result<ProcessedFile> {
        let mut base_content = String::new();

        // Start with the last file (lowest priority) and work backwards
        for file_path in files.iter().rev() {
            let content = std::fs::read_to_string(file_path).map_err(RusteloError::Io)?;

            if base_content.is_empty() {
                base_content = content;
            } else {
                base_content = AssetMerger::merge_json(&base_content, &content)?;
            }
        }

        // Apply variable substitution
        let processed = self.substitute_variables(&base_content, &context.variables)?;

        std::fs::write(target_path, processed).map_err(RusteloError::Io)?;

        Ok(ProcessedFile {
            target_path: target_path.to_path_buf(),
            source_path: files[0].clone(),
            merged: true,
            processor: "config".to_string(),
        })
    }

    async fn merge_toml_files(
        &self,
        files: &[PathBuf],
        target_path: &Path,
        context: &ProcessingContext,
    ) -> Result<ProcessedFile> {
        let mut base_content = String::new();

        // Start with the last file (lowest priority) and work backwards
        for file_path in files.iter().rev() {
            let content = std::fs::read_to_string(file_path).map_err(RusteloError::Io)?;

            if base_content.is_empty() {
                base_content = content;
            } else {
                base_content = AssetMerger::merge_toml(&base_content, &content)?;
            }
        }

        // Apply variable substitution
        let processed = self.substitute_variables(&base_content, &context.variables)?;

        std::fs::write(target_path, processed).map_err(RusteloError::Io)?;

        Ok(ProcessedFile {
            target_path: target_path.to_path_buf(),
            source_path: files[0].clone(),
            merged: true,
            processor: "config".to_string(),
        })
    }
}

/// Content file processor for markdown, HTML, etc.
pub struct ContentFileProcessor;

#[async_trait]
impl FileProcessor for ContentFileProcessor {
    fn handles_extensions(&self) -> Vec<&'static str> {
        vec![
            "md", "markdown", "html", "htm", "txt", "rst", "adoc", "asciidoc", "wiki", "textile",
        ]
    }

    async fn process_file(
        &self,
        source_path: &Path,
        target_path: &Path,
        context: &ProcessingContext,
    ) -> Result<ProcessedFile> {
        // Create target directory
        if let Some(parent) = target_path.parent() {
            std::fs::create_dir_all(parent).map_err(RusteloError::Io)?;
        }

        // Read and process content
        let content = std::fs::read_to_string(source_path).map_err(RusteloError::Io)?;

        let processed_content = self.process_content(&content, context)?;

        // Write processed content
        std::fs::write(target_path, processed_content).map_err(RusteloError::Io)?;

        Ok(ProcessedFile {
            target_path: target_path.to_path_buf(),
            source_path: source_path.to_path_buf(),
            merged: false,
            processor: "content".to_string(),
        })
    }
}

impl ContentFileProcessor {
    fn process_content(&self, content: &str, context: &ProcessingContext) -> Result<String> {
        let mut result = content.to_string();

        // Apply variable substitution
        for (key, value) in &context.variables {
            let pattern = format!("{{{{{}}}}}", key);
            result = result.replace(&pattern, value);
        }

        // TODO: Add markdown processing, link resolution, etc.

        Ok(result)
    }
}

/// Static file processor for images, fonts, etc.
pub struct StaticFileProcessor;

#[async_trait]
impl FileProcessor for StaticFileProcessor {
    fn handles_extensions(&self) -> Vec<&'static str> {
        vec![
            // Images
            "png", "jpg", "jpeg", "gif", "svg", "webp", "avif", "ico", // Fonts
            "woff", "woff2", "ttf", "otf", "eot", // Other static assets
            "css", "scss", "sass", "less", "pdf", "zip", "tar", "gz",
        ]
    }

    async fn process_file(
        &self,
        source_path: &Path,
        target_path: &Path,
        _context: &ProcessingContext,
    ) -> Result<ProcessedFile> {
        // Create target directory
        if let Some(parent) = target_path.parent() {
            std::fs::create_dir_all(parent).map_err(RusteloError::Io)?;
        }

        // Simple copy for static files
        std::fs::copy(source_path, target_path).map_err(RusteloError::Io)?;

        Ok(ProcessedFile {
            target_path: target_path.to_path_buf(),
            source_path: source_path.to_path_buf(),
            merged: false,
            processor: "static".to_string(),
        })
    }
}
