use std::fs;
use std::path::{Path, PathBuf};

use chrono::Utc;
use serde::{Deserialize, Serialize};

use super::error::{ClosureError, Result};
use super::fsutil::{atomic_write, ensure_parent_dir};

const BACKUP_FORMAT_VERSION: u32 = 1;

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct BackupEntry {
    pub id: String,
    pub created_at: String,
    pub files: Vec<String>,
}

pub struct BackupManager {
    root_dir: PathBuf,
}

impl BackupManager {
    pub fn new(root_dir: &Path) -> Self {
        Self {
            root_dir: root_dir.to_path_buf(),
        }
    }

    fn manifest_path(&self) -> PathBuf {
        self.root_dir.join("manifest.json")
    }

    fn entry_dir(&self, id: &str) -> PathBuf {
        self.root_dir.join(id)
    }

    pub fn create_snapshot(
        &self,
        source_dir: &Path,
        tracked_files: &[&str],
    ) -> Result<Option<BackupEntry>> {
        fs::create_dir_all(&self.root_dir)?;

        let mut copied = Vec::new();
        for rel in tracked_files {
            if source_dir.join(rel).exists() {
                copied.push((*rel).to_string());
            }
        }
        if copied.is_empty() {
            return Ok(None);
        }

        let now = Utc::now();
        let id = now
            .timestamp_nanos_opt()
            .ok_or_else(|| ClosureError::Backup("failed to create backup timestamp".to_string()))?
            .to_string();
        let entry = BackupEntry {
            id: id.clone(),
            created_at: now.to_rfc3339(),
            files: copied.clone(),
        };

        let snapshot_dir = self.entry_dir(&id);
        for rel in &copied {
            let src = source_dir.join(rel);
            let dst = snapshot_dir.join(rel);
            ensure_parent_dir(&dst)?;
            fs::copy(src, dst)?;
        }

        let mut entries = self.list()?;
        entries.push(entry.clone());
        self.write_manifest(&entries)?;
        Ok(Some(entry))
    }

    pub fn list(&self) -> Result<Vec<BackupEntry>> {
        let path = self.manifest_path();
        if !path.exists() {
            return Ok(Vec::new());
        }
        let contents = fs::read_to_string(path)?;
        let manifest: BackupManifest = serde_json::from_str(&contents)?;
        Ok(manifest.entries)
    }

    pub fn inspect(&self, id: &str) -> Result<BackupEntry> {
        self.list()?
            .into_iter()
            .find(|entry| entry.id == id)
            .ok_or_else(|| ClosureError::Backup(format!("backup not found: {id}")))
    }

    pub fn restore(&self, id: &str, target_dir: &Path) -> Result<BackupEntry> {
        let entry = self.inspect(id)?;
        let snapshot_dir = self.entry_dir(id);

        if !snapshot_dir.exists() {
            return Err(ClosureError::Backup(format!(
                "backup payload missing: {}",
                snapshot_dir.display()
            )));
        }

        for rel in &entry.files {
            let src = snapshot_dir.join(rel);
            if !src.exists() {
                return Err(ClosureError::Backup(format!(
                    "backup file missing: {}",
                    src.display()
                )));
            }
            let dst = target_dir.join(rel);
            let bytes = fs::read(src)?;
            atomic_write(&dst, &bytes)?;
        }

        Ok(entry)
    }
    fn write_manifest(&self, entries: &[BackupEntry]) -> Result<()> {
        let manifest = BackupManifest {
            version: BACKUP_FORMAT_VERSION,
            entries: entries.to_vec(),
        };
        let bytes = serde_json::to_vec_pretty(&manifest)?;
        atomic_write(&self.manifest_path(), &bytes)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct BackupManifest {
    version: u32,
    entries: Vec<BackupEntry>,
}
