use clap::{Args, Subcommand};

use super::{BANNER, HELP_STYLES};

/// Delete cached or stale jackin data
#[derive(Debug, Subcommand, PartialEq, Eq)]
pub enum PruneCommand {
    /// Delete cached role repositories
    ///
    /// Removes the local role-repo cache. Role repos are re-cloned
    /// automatically the next time a role is launched.
    #[command(before_help = BANNER, styles = HELP_STYLES)]
    Roles,
    /// Delete shared caches
    ///
    /// Removes the shared cache directory. Includes the compiled terminfo
    /// cache and version-check results. All caches regenerate automatically.
    #[command(before_help = BANNER, styles = HELP_STYLES)]
    Cache,
    /// Remove unused jackin-managed Docker images
    ///
    /// Removes jk_* images that have no role containers (running or stopped).
    /// Images still used by a role container are skipped.
    #[command(before_help = BANNER, styles = HELP_STYLES)]
    Images,
    /// Purge on-disk state for instances
    ///
    /// By default removes state directories and index entries for instances
    /// in terminal statuses: `clean_exited`, `superseded`, `failed_setup`,
    /// and `purged` tombstones. Running and recoverable instances are skipped.
    ///
    /// Pass `--all` to also stop and remove running instances (equivalent to
    /// `jackin eject <selector> --purge` for every instance).
    #[command(before_help = BANNER, styles = HELP_STYLES)]
    Instances(PruneInstancesArgs),
    /// Remove all prunable data
    ///
    /// Runs in order: prune instances, prune images, prune roles, prune cache.
    /// By default skips running instances; pass `--all` to stop and remove them too.
    #[command(before_help = BANNER, styles = HELP_STYLES)]
    System(PruneSystemArgs),
}

/// Arguments for `jackin prune instances`
#[derive(Debug, Args, PartialEq, Eq)]
#[command(before_help = BANNER, styles = HELP_STYLES)]
pub struct PruneInstancesArgs {
    /// Also stop and remove running or recoverable instances
    #[arg(long, short = 'a')]
    pub all: bool,
}

/// Arguments for `jackin prune system`
#[derive(Debug, Args, PartialEq, Eq)]
#[command(before_help = BANNER, styles = HELP_STYLES)]
pub struct PruneSystemArgs {
    /// Skip the confirmation prompt
    #[arg(long, short = 'y')]
    pub yes: bool,
    /// Also stop and remove running or recoverable instances
    #[arg(long, short = 'a')]
    pub all: bool,
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::cli::Cli;
    use clap::Parser;

    fn strip_ansi(s: &str) -> String {
        let mut result = String::with_capacity(s.len());
        let mut chars = s.chars();
        while let Some(ch) = chars.next() {
            if ch == '\x1b' {
                for inner in chars.by_ref() {
                    if inner.is_ascii_alphabetic() {
                        break;
                    }
                }
            } else {
                result.push(ch);
            }
        }
        result
    }

    fn help_text(args: &[&str]) -> String {
        let err = Cli::try_parse_from(args).unwrap_err();
        strip_ansi(&err.to_string())
    }

    #[test]
    fn prune_help_lists_subcommands() {
        let help = help_text(&["jackin", "prune", "--help"]);
        for sub in ["roles", "cache", "images", "instances", "system"] {
            assert!(help.contains(sub), "missing subcommand: {sub}");
        }
    }

    #[test]
    fn prune_roles_parses() {
        let cli = Cli::try_parse_from(["jackin", "prune", "roles"]).unwrap();
        assert!(matches!(
            cli.command,
            Some(crate::cli::Command::Prune(PruneCommand::Roles))
        ));
    }

    #[test]
    fn prune_cache_parses() {
        let cli = Cli::try_parse_from(["jackin", "prune", "cache"]).unwrap();
        assert!(matches!(
            cli.command,
            Some(crate::cli::Command::Prune(PruneCommand::Cache))
        ));
    }

    #[test]
    fn prune_images_parses() {
        let cli = Cli::try_parse_from(["jackin", "prune", "images"]).unwrap();
        assert!(matches!(
            cli.command,
            Some(crate::cli::Command::Prune(PruneCommand::Images))
        ));
    }

    #[test]
    fn prune_instances_parses() {
        let cli = Cli::try_parse_from(["jackin", "prune", "instances"]).unwrap();
        assert!(matches!(
            cli.command,
            Some(crate::cli::Command::Prune(PruneCommand::Instances(
                PruneInstancesArgs { all: false }
            )))
        ));
    }

    #[test]
    fn prune_instances_all_flag_parses() {
        let cli = Cli::try_parse_from(["jackin", "prune", "instances", "--all"]).unwrap();
        assert!(matches!(
            cli.command,
            Some(crate::cli::Command::Prune(PruneCommand::Instances(
                PruneInstancesArgs { all: true }
            )))
        ));
    }

    #[test]
    fn prune_system_defaults_yes_false_all_false() {
        let cli = Cli::try_parse_from(["jackin", "prune", "system"]).unwrap();
        assert!(matches!(
            cli.command,
            Some(crate::cli::Command::Prune(PruneCommand::System(
                PruneSystemArgs {
                    yes: false,
                    all: false
                }
            )))
        ));
    }

    #[test]
    fn prune_system_yes_flag_parses() {
        let cli = Cli::try_parse_from(["jackin", "prune", "system", "--yes"]).unwrap();
        assert!(matches!(
            cli.command,
            Some(crate::cli::Command::Prune(PruneCommand::System(
                PruneSystemArgs {
                    yes: true,
                    all: false
                }
            )))
        ));
    }

    #[test]
    fn prune_system_short_y_flag_parses() {
        let cli = Cli::try_parse_from(["jackin", "prune", "system", "-y"]).unwrap();
        assert!(matches!(
            cli.command,
            Some(crate::cli::Command::Prune(PruneCommand::System(
                PruneSystemArgs {
                    yes: true,
                    all: false
                }
            )))
        ));
    }

    #[test]
    fn prune_system_all_flag_parses() {
        let cli = Cli::try_parse_from(["jackin", "prune", "system", "--all"]).unwrap();
        assert!(matches!(
            cli.command,
            Some(crate::cli::Command::Prune(PruneCommand::System(
                PruneSystemArgs {
                    yes: false,
                    all: true
                }
            )))
        ));
    }
}
