use anyhow::{Context, Result};
use chrono::{DateTime, Datelike, Local, Timelike};
use std::path::{Path, PathBuf};

pub struct OutputDir {
    base_path: PathBuf,
    backup_enabled: bool,
    backup_dir: PathBuf,
}

impl OutputDir {
    pub fn new() -> Self {
        let base_path = std::env::var("MOMENTRY_OUTPUT_DIR")
            .map(PathBuf::from)
            .unwrap_or_else(|_| PathBuf::from("./output"));

        let backup_enabled = std::env::var("MOMENTRY_BACKUP_ENABLED")
            .map(|v| v == "true")
            .unwrap_or(false);

        let backup_dir = std::env::var("MOMENTRY_BACKUP_DIR")
            .map(PathBuf::from)
            .unwrap_or_else(|_| PathBuf::from("/Users/accusys/momentry/backup/momentry"));

        Self {
            base_path,
            backup_enabled,
            backup_dir,
        }
    }

    pub fn get_base_path(&self) -> &Path {
        &self.base_path
    }

    pub fn get_backup_dir(&self) -> &Path {
        &self.backup_dir
    }

    pub fn ensure_dir(&self) -> Result<()> {
        std::fs::create_dir_all(&self.base_path).context(format!(
            "Failed to create output directory: {:?}",
            self.base_path
        ))?;

        if self.backup_enabled {
            std::fs::create_dir_all(&self.backup_dir).context(format!(
                "Failed to create backup directory: {:?}",
                self.backup_dir
            ))?;
        }

        Ok(())
    }

    pub fn get_output_path(&self, uuid: &str, extension: &str) -> PathBuf {
        self.base_path.join(format!("{}.{}", uuid, extension))
    }

    fn get_timestamp() -> String {
        let now = Local::now();
        format!(
            "{:04}{:02}{:02}_{:02}{:02}{:02}",
            now.year(),
            now.month(),
            now.day(),
            now.hour(),
            now.minute(),
            now.second()
        )
    }

    pub fn get_backup_path(&self, uuid: &str, extension: &str) -> Option<PathBuf> {
        if !self.backup_enabled {
            return None;
        }

        let timestamp = Self::get_timestamp();
        let filename = format!("momentry_data_{}_{}.{}", timestamp, uuid, extension);

        Some(self.backup_dir.join(filename))
    }

    pub fn backup_file(&self, uuid: &str, extension: &str) -> Result<Option<PathBuf>> {
        if !self.backup_enabled {
            return Ok(None);
        }

        let source = self.get_output_path(uuid, extension);
        if !source.exists() {
            return Ok(None);
        }

        let backup_path = match self.get_backup_path(uuid, extension) {
            Some(path) => path,
            None => return Ok(None),
        };

        std::fs::copy(&source, &backup_path)
            .context(format!("Failed to backup file to: {:?}", backup_path))?;

        let sha256_path = backup_path.with_extension(format!("{}.sha256", extension));

        let source_content = std::fs::read(&source)?;
        let hash = format!("{:x}", md5::compute(&source_content));
        std::fs::write(
            &sha256_path,
            format!(
                "{}  {}\n",
                hash,
                backup_path.file_name().unwrap().to_string_lossy()
            ),
        )?;

        Ok(Some(backup_path))
    }

    pub fn cleanup_old_backups(&self, days: u32) -> Result<u32> {
        if !self.backup_enabled {
            return Ok(0);
        }

        if !self.backup_dir.exists() {
            return Ok(0);
        }

        let cutoff = Local::now() - chrono::Duration::days(days as i64);
        let mut deleted_count = 0;

        for entry in std::fs::read_dir(&self.backup_dir)? {
            let entry = entry?;
            let path = entry.path();

            if !path.is_file() {
                continue;
            }

            if let Some(name) = path.file_name() {
                let name_str = name.to_string_lossy();
                if name_str.starts_with("momentry_data_") && name_str.len() == 43 {
                    let date_part = &name_str[14..22];
                    if let Ok(date) =
                        DateTime::parse_from_str(&format!("{} 000000", date_part), "%Y%m%d %H%M%S")
                    {
                        if date.with_timezone(&Local) < cutoff {
                            std::fs::remove_file(&path)?;
                            deleted_count += 1;

                            let sha256_path = path.with_extension("sha256");
                            if sha256_path.exists() {
                                let _ = std::fs::remove_file(sha256_path);
                            }
                        }
                    }
                }
            }
        }

        Ok(deleted_count)
    }

    pub fn list_backups(&self) -> Result<Vec<BackupInfo>> {
        if !self.backup_dir.exists() {
            return Ok(vec![]);
        }

        let mut backups = Vec::new();

        for entry in std::fs::read_dir(&self.backup_dir)? {
            let entry = entry?;
            let path = entry.path();

            if !path.is_file() {
                continue;
            }

            if let Some(name) = path.file_name() {
                let name_str = name.to_string_lossy();
                if name_str.starts_with("momentry_data_") && name_str.ends_with(".sha256") {
                    continue;
                }

                if name_str.starts_with("momentry_data_") {
                    let date_part = &name_str[14..22];
                    backups.push(BackupInfo {
                        filename: name_str.to_string(),
                        date: date_part.to_string(),
                        path: path.clone(),
                    });
                }
            }
        }

        backups.sort_by(|a, b| b.date.cmp(&a.date));
        Ok(backups)
    }

    pub fn verify_backup(&self, backup_path: &Path) -> Result<bool> {
        let sha256_path = backup_path.with_extension("sha256");

        if !sha256_path.exists() {
            return Ok(false);
        }

        let sha256_content = std::fs::read_to_string(&sha256_path)?;
        let expected_hash = sha256_content.split_whitespace().next().unwrap_or("");

        let source_content = std::fs::read(backup_path)?;
        let actual_hash = format!("{:x}", md5::compute(&source_content));

        Ok(expected_hash == actual_hash)
    }
}

impl Default for OutputDir {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Debug, Clone)]
pub struct BackupInfo {
    pub filename: String,
    pub date: String,
    pub path: PathBuf,
}
