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

use crate::core::config;

#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SnapshotTier {
    Hot,
    Warm,
    Cold,
}

impl std::fmt::Display for SnapshotTier {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SnapshotTier::Hot => write!(f, "hot"),
            SnapshotTier::Warm => write!(f, "warm"),
            SnapshotTier::Cold => write!(f, "cold"),
        }
    }
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct SnapshotStatus {
    pub file_uuid: String,
    pub tier: SnapshotTier,
    pub hits: u64,
    pub types: Vec<String>,
    pub generated_at: Option<String>,
}

#[derive(Debug, Clone)]
pub struct SnapshotManager {
    base_dir: PathBuf,
}

impl SnapshotManager {
    pub fn new(user_dir: &str) -> Self {
        let snapshot_dir_name = config::snapshot::SNAPSHOT_DIR_NAME.as_str();
        let base_dir = Path::new(user_dir).join(snapshot_dir_name);
        Self { base_dir }
    }

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

    pub fn file_snapshot_dir(&self, file_uuid: &str) -> PathBuf {
        self.base_dir.join(file_uuid)
    }

    pub fn file_type_dir(&self, file_uuid: &str, snapshot_type: &str) -> PathBuf {
        self.base_dir.join(file_uuid).join(snapshot_type)
    }

    pub fn identity_snapshot_dir(&self, identity_uuid: &str) -> PathBuf {
        self.base_dir.join("identities").join(identity_uuid)
    }

    pub fn identity_face_dir(&self, identity_uuid: &str) -> PathBuf {
        self.base_dir
            .join("identities")
            .join(identity_uuid)
            .join("faces")
    }

    pub fn ensure_file_dirs(&self, file_uuid: &str) -> std::io::Result<()> {
        let dir = self.file_snapshot_dir(file_uuid);
        std::fs::create_dir_all(&dir)?;
        for snap_type in ["faces", "logos", "products", "ocr"] {
            std::fs::create_dir_all(dir.join(snap_type))?;
        }
        Ok(())
    }

    pub fn ensure_identity_dirs(&self, identity_uuid: &str) -> std::io::Result<()> {
        let dir = self.identity_snapshot_dir(identity_uuid);
        std::fs::create_dir_all(&dir)?;
        std::fs::create_dir_all(dir.join("faces"))?;
        Ok(())
    }

    pub fn compute_tier(hits: u64) -> SnapshotTier {
        let threshold = *config::snapshot::HOT_THRESHOLD;
        if hits >= threshold {
            SnapshotTier::Hot
        } else if hits > 0 {
            SnapshotTier::Warm
        } else {
            SnapshotTier::Cold
        }
    }

    pub fn tier_ttl(&self, tier: SnapshotTier) -> u64 {
        match tier {
            SnapshotTier::Hot => *config::snapshot::HOT_TTL_SECS,
            SnapshotTier::Warm => *config::snapshot::WARM_TTL_SECS,
            SnapshotTier::Cold => 0,
        }
    }

    pub fn snapshot_exists(&self, file_uuid: &str, snapshot_type: &str) -> bool {
        self.file_type_dir(file_uuid, snapshot_type).exists()
    }

    pub fn list_snapshot_types(&self, file_uuid: &str) -> Vec<String> {
        let dir = self.file_snapshot_dir(file_uuid);
        if !dir.exists() {
            return Vec::new();
        }
        std::fs::read_dir(&dir)
            .into_iter()
            .flatten()
            .filter_map(|e| e.ok())
            .filter(|e| e.path().is_dir())
            .filter_map(|e| e.file_name().to_str().map(String::from))
            .collect()
    }

    pub fn remove_file_snapshots(&self, file_uuid: &str) -> std::io::Result<()> {
        let dir = self.file_snapshot_dir(file_uuid);
        if dir.exists() {
            std::fs::remove_dir_all(&dir)?;
        }
        Ok(())
    }

    pub fn remove_identity_snapshots(&self, identity_uuid: &str) -> std::io::Result<()> {
        let dir = self.identity_snapshot_dir(identity_uuid);
        if dir.exists() {
            std::fs::remove_dir_all(&dir)?;
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn create_test_manager() -> (SnapshotManager, tempfile::TempDir) {
        let temp_dir = tempfile::tempdir().unwrap();
        let manager = SnapshotManager::new(temp_dir.path().to_str().unwrap());
        (manager, temp_dir)
    }

    #[test]
    fn test_compute_tier_hot() {
        assert_eq!(SnapshotManager::compute_tier(5), SnapshotTier::Hot);
        assert_eq!(SnapshotManager::compute_tier(10), SnapshotTier::Hot);
        assert_eq!(SnapshotManager::compute_tier(100), SnapshotTier::Hot);
    }

    #[test]
    fn test_compute_tier_warm() {
        assert_eq!(SnapshotManager::compute_tier(1), SnapshotTier::Warm);
        assert_eq!(SnapshotManager::compute_tier(4), SnapshotTier::Warm);
    }

    #[test]
    fn test_compute_tier_cold() {
        assert_eq!(SnapshotManager::compute_tier(0), SnapshotTier::Cold);
    }

    #[test]
    fn test_tier_display() {
        assert_eq!(SnapshotTier::Hot.to_string(), "hot");
        assert_eq!(SnapshotTier::Warm.to_string(), "warm");
        assert_eq!(SnapshotTier::Cold.to_string(), "cold");
    }

    #[test]
    fn test_ensure_file_dirs_creates_structure() {
        let (manager, _temp) = create_test_manager();
        let file_uuid = "test_file_123";

        manager.ensure_file_dirs(file_uuid).unwrap();

        assert!(manager.file_snapshot_dir(file_uuid).exists());
        assert!(manager.file_type_dir(file_uuid, "faces").exists());
        assert!(manager.file_type_dir(file_uuid, "logos").exists());
        assert!(manager.file_type_dir(file_uuid, "products").exists());
        assert!(manager.file_type_dir(file_uuid, "ocr").exists());
    }

    #[test]
    fn test_ensure_identity_dirs_creates_structure() {
        let (manager, _temp) = create_test_manager();
        let identity_uuid = "test_identity_456";

        manager.ensure_identity_dirs(identity_uuid).unwrap();

        assert!(manager.identity_snapshot_dir(identity_uuid).exists());
        assert!(manager.identity_face_dir(identity_uuid).exists());
    }

    #[test]
    fn test_list_snapshot_types_empty() {
        let (manager, _temp) = create_test_manager();
        let types = manager.list_snapshot_types("nonexistent");
        assert!(types.is_empty());
    }

    #[test]
    fn test_list_snapshot_types_after_creation() {
        let (manager, _temp) = create_test_manager();
        let file_uuid = "test_file_789";

        manager.ensure_file_dirs(file_uuid).unwrap();
        let types = manager.list_snapshot_types(file_uuid);

        assert_eq!(types.len(), 4);
        assert!(types.contains(&"faces".to_string()));
        assert!(types.contains(&"logos".to_string()));
        assert!(types.contains(&"products".to_string()));
        assert!(types.contains(&"ocr".to_string()));
    }

    #[test]
    fn test_remove_file_snapshots() {
        let (manager, _temp) = create_test_manager();
        let file_uuid = "test_file_remove";

        manager.ensure_file_dirs(file_uuid).unwrap();
        assert!(manager.file_snapshot_dir(file_uuid).exists());

        manager.remove_file_snapshots(file_uuid).unwrap();
        assert!(!manager.file_snapshot_dir(file_uuid).exists());
    }

    #[test]
    fn test_remove_identity_snapshots() {
        let (manager, _temp) = create_test_manager();
        let identity_uuid = "test_identity_remove";

        manager.ensure_identity_dirs(identity_uuid).unwrap();
        assert!(manager.identity_snapshot_dir(identity_uuid).exists());

        manager.remove_identity_snapshots(identity_uuid).unwrap();
        assert!(!manager.identity_snapshot_dir(identity_uuid).exists());
    }

    #[test]
    fn test_snapshot_exists() {
        let (manager, _temp) = create_test_manager();
        let file_uuid = "test_exists";

        assert!(!manager.snapshot_exists(file_uuid, "faces"));

        manager.ensure_file_dirs(file_uuid).unwrap();
        assert!(manager.snapshot_exists(file_uuid, "faces"));
        assert!(!manager.snapshot_exists(file_uuid, "nonexistent"));
    }

    #[test]
    fn test_tier_ttl() {
        let (manager, _temp) = create_test_manager();

        let hot_ttl = manager.tier_ttl(SnapshotTier::Hot);
        assert_eq!(hot_ttl, *config::snapshot::HOT_TTL_SECS);

        let warm_ttl = manager.tier_ttl(SnapshotTier::Warm);
        assert_eq!(warm_ttl, *config::snapshot::WARM_TTL_SECS);

        let cold_ttl = manager.tier_ttl(SnapshotTier::Cold);
        assert_eq!(cold_ttl, 0);
    }
}
