use anyhow::{Context, Result};
use std::process::Command;

#[derive(Debug, Clone)]
pub struct ZfsEntry {
    pub name: String,
    pub full_name: String,
    pub entry_type: ZfsType,
    pub used: u64,
    pub referenced: u64,
    pub available: u64,
    pub creation: String,
    pub children: Vec<ZfsEntry>,
    pub snapshots: Vec<ZfsSnapshot>,
}

#[derive(Debug, Clone)]
pub struct ZfsSnapshot {
    pub name: String,
    pub full_name: String,
    pub used: u64,
    pub referenced: u64,
    pub creation: String,
}

#[derive(Debug, Clone, PartialEq)]
pub enum ZfsType {
    Pool,
    Filesystem,
    Volume,
}

impl ZfsEntry {
    pub fn short_name(&self) -> &str {
        self.name.rsplit('/').next().unwrap_or(&self.name)
    }

    pub fn has_children(&self) -> bool {
        !self.children.is_empty() || !self.snapshots.is_empty()
    }
}

impl ZfsSnapshot {
    pub fn short_name(&self) -> &str {
        self.name.rsplit('@').next().unwrap_or(&self.name)
    }
}

pub fn get_zfs_tree() -> Result<Vec<ZfsEntry>> {
    // Get all datasets
    let output = Command::new("zfs")
        .args([
            "list",
            "-H",
            "-o",
            "name,type,used,referenced,available,creation",
            "-p", // parseable (bytes)
            "-t",
            "filesystem,volume",
        ])
        .output()
        .context("Failed to run zfs list")?;

    if !output.status.success() {
        anyhow::bail!(
            "zfs list failed: {}",
            String::from_utf8_lossy(&output.stderr)
        );
    }

    let datasets = parse_zfs_list(&String::from_utf8_lossy(&output.stdout))?;

    // Get all snapshots
    let snap_output = Command::new("zfs")
        .args([
            "list",
            "-H",
            "-o",
            "name,used,referenced,creation",
            "-p",
            "-t",
            "snapshot",
            "-s",
            "used", // sort by used (ascending)
        ])
        .output()
        .context("Failed to run zfs list snapshots")?;

    let snapshots = if snap_output.status.success() {
        parse_snapshots(&String::from_utf8_lossy(&snap_output.stdout))?
    } else {
        Vec::new()
    };

    // Build tree structure
    let tree = build_tree(datasets, snapshots);

    Ok(tree)
}

fn parse_zfs_list(output: &str) -> Result<Vec<(String, ZfsType, u64, u64, u64, String)>> {
    let mut entries = Vec::new();

    for line in output.lines() {
        let parts: Vec<&str> = line.split('\t').collect();
        if parts.len() >= 6 {
            let name = parts[0].to_string();
            let entry_type = match parts[1] {
                "filesystem" => ZfsType::Filesystem,
                "volume" => ZfsType::Volume,
                _ => continue,
            };
            let used = parts[2].parse().unwrap_or(0);
            let referenced = parts[3].parse().unwrap_or(0);
            let available = parts[4].parse().unwrap_or(0);
            let creation = parts[5].to_string();

            entries.push((name, entry_type, used, referenced, available, creation));
        }
    }

    Ok(entries)
}

fn parse_snapshots(output: &str) -> Result<Vec<ZfsSnapshot>> {
    let mut snapshots = Vec::new();

    for line in output.lines() {
        let parts: Vec<&str> = line.split('\t').collect();
        if parts.len() >= 4 {
            let full_name = parts[0].to_string();
            let name = full_name.clone();
            let used = parts[1].parse().unwrap_or(0);
            let referenced = parts[2].parse().unwrap_or(0);
            let creation = parts[3].to_string();

            snapshots.push(ZfsSnapshot {
                name,
                full_name,
                used,
                referenced,
                creation,
            });
        }
    }

    // Sort descending by used (largest first)
    snapshots.sort_by(|a, b| b.used.cmp(&a.used));

    Ok(snapshots)
}

fn build_tree(
    datasets: Vec<(String, ZfsType, u64, u64, u64, String)>,
    snapshots: Vec<ZfsSnapshot>,
) -> Vec<ZfsEntry> {
    let mut root_entries: Vec<ZfsEntry> = Vec::new();

    // First, create all entries
    let mut all_entries: Vec<ZfsEntry> = datasets
        .into_iter()
        .map(|(name, entry_type, used, referenced, available, creation)| {
            // Find snapshots for this dataset
            let dataset_snaps: Vec<ZfsSnapshot> = snapshots
                .iter()
                .filter(|s| s.full_name.starts_with(&format!("{}@", name)))
                .cloned()
                .collect();

            ZfsEntry {
                name: name.clone(),
                full_name: name,
                entry_type,
                used,
                referenced,
                available,
                creation,
                children: Vec::new(),
                snapshots: dataset_snaps,
            }
        })
        .collect();

    // Sort by used (largest first)
    all_entries.sort_by(|a, b| b.used.cmp(&a.used));

    // Build hierarchy
    // Root entries have no '/' or are pool roots
    for entry in all_entries {
        if !entry.name.contains('/') {
            root_entries.push(entry);
        } else {
            // Find parent and add as child
            let parent_name = entry.name.rsplit_once('/').map(|(p, _)| p).unwrap_or("");
            if let Some(parent) = find_entry_mut(&mut root_entries, parent_name) {
                parent.children.push(entry);
            } else {
                // Orphan entry, add to root
                root_entries.push(entry);
            }
        }
    }

    // Sort children by used
    sort_children(&mut root_entries);

    root_entries
}

fn find_entry_mut<'a>(entries: &'a mut [ZfsEntry], name: &str) -> Option<&'a mut ZfsEntry> {
    for entry in entries.iter_mut() {
        if entry.name == name {
            return Some(entry);
        }
        if let Some(found) = find_entry_mut(&mut entry.children, name) {
            return Some(found);
        }
    }
    None
}

fn sort_children(entries: &mut [ZfsEntry]) {
    for entry in entries.iter_mut() {
        entry.children.sort_by(|a, b| b.used.cmp(&a.used));
        sort_children(&mut entry.children);
    }
}

pub fn delete_snapshot(name: &str) -> Result<()> {
    let output = Command::new("zfs")
        .args(["destroy", name])
        .output()
        .context("Failed to run zfs destroy")?;

    if !output.status.success() {
        anyhow::bail!(
            "zfs destroy failed: {}",
            String::from_utf8_lossy(&output.stderr)
        );
    }

    Ok(())
}

pub fn format_size(bytes: u64) -> String {
    const KB: u64 = 1024;
    const MB: u64 = KB * 1024;
    const GB: u64 = MB * 1024;
    const TB: u64 = GB * 1024;

    if bytes >= TB {
        format!("{:.1}T", bytes as f64 / TB as f64)
    } else if bytes >= GB {
        format!("{:.1}G", bytes as f64 / GB as f64)
    } else if bytes >= MB {
        format!("{:.1}M", bytes as f64 / MB as f64)
    } else if bytes >= KB {
        format!("{:.1}K", bytes as f64 / KB as f64)
    } else {
        format!("{}B", bytes)
    }
}
