use clap::{Parser, Subcommand, ValueEnum};
use std::path::PathBuf;

#[derive(Parser, Debug)]
#[command(
    name = "bcmr",
    about = "Better Copy Move Remove (BCMR) - A modern CLI tool for file operations",
    version,
    author
)]
pub struct Cli {
    #[command(subcommand)]
    pub command: Commands,
}

#[derive(Clone, Debug, ValueEnum)]
pub enum Shell {
    Bash,
    Zsh,
    Fish,
}

#[derive(Clone, Debug)]
pub enum SparseMode {
    Always,
    Auto,
    Never,
}

impl std::fmt::Display for Shell {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Shell::Bash => write!(f, "bash"),
            Shell::Zsh => write!(f, "zsh"),
            Shell::Fish => write!(f, "fish"),
        }
    }
}

#[derive(Subcommand, Debug)]
pub enum Commands {
    /// Initialize shell integration
    Init {
        /// Shell to initialize (bash, zsh, fish)
        shell: Shell,

        /// Command prefix (default: no prefix if empty).
        /// Acts as a "base" for aliases.
        #[arg(long, num_args = 0..=1, default_missing_value = "")]
        cmd: Option<String>,

        /// Explicit command prefix (overrides cmd if present)
        #[arg(long, requires = "cmd")]
        prefix: Option<String>,

        /// Command suffix
        #[arg(long, requires = "cmd")]
        suffix: Option<String>,

        /// Path to add to PATH
        #[arg(long)]
        path: Option<PathBuf>,

        /// No command prefix
        #[arg(long)]
        no_cmd: bool,
    },

    /// Copy files or directories
    Copy {
        /// Source files and destination directory
        /// Last argument is the destination
        #[arg(required = true, num_args = 2..)]
        paths: Vec<PathBuf>,

        /// Recursively copy directories
        #[arg(short, long)]
        recursive: bool,

        /// Preserve file attributes
        #[arg(short, long)]
        preserve: bool,

        /// Overwrite existing files
        #[arg(short, long)]
        force: bool,

        /// Skip confirmation prompt when using force
        #[arg(short = 'y', long = "yes")]
        yes: bool,

        /// Explain what is being done
        #[arg(short = 'v', long)]
        verbose: bool,

        /// Exclude paths matching regex pattern
        #[arg(short = 'e', long)]
        exclude: Option<Vec<String>>,

        /// Enable inline TUI mode (classic 3-line display)
        #[arg(short, long)]
        tui: bool,

        /// Run in dry-run mode (no changes)
        #[arg(short = 'n', long)]
        dry_run: bool,

        /// Hidden test mode for simulation
        #[arg(long, hide = true, value_parser = parse_test_mode)]
        test_mode: Option<TestMode>,

        /// Verify file integrity after copy
        #[arg(short = 'V', long, default_value_t = false)]
        verify: bool,

        /// Resume interrupted copy
        #[arg(short = 'C', long, default_value_t = false)]
        resume: bool,

        /// Use strict hash verification for resume
        #[arg(short = 's', long, default_value_t = false)]
        strict: bool,

        /// Append data to existing file (ignores mtime, checks size only)
        #[arg(short = 'a', long, default_value_t = false)]
        append: bool,

        /// Sync data to disk after copy (fsync)
        #[arg(long, default_value_t = false)]
        sync: bool,

        /// Use Copy-on-Write (reflink) if supported
        /// Modes: force, auto (default), disable
        #[arg(long, num_args = 0..=1, default_missing_value = "auto")]
        reflink: Option<String>,

        /// Control sparse file creation
        /// Modes: force, auto (default), disable
        #[arg(long, num_args = 0..=1, default_missing_value = "auto")]
        sparse: Option<String>,

        /// Number of parallel SCP transfers
        #[arg(short = 'P', long)]
        parallel: Option<usize>,
    },

    /// Move files or directories
    Move {
        /// Source files and destination directory
        /// Last argument is the destination
        #[arg(required = true, num_args = 2..)]
        paths: Vec<PathBuf>,

        /// Recursively move directories
        #[arg(short, long)]
        recursive: bool,

        /// Preserve file attributes
        #[arg(short, long)]
        preserve: bool,

        /// Overwrite existing files
        #[arg(short, long)]
        force: bool,

        /// Skip confirmation prompt when using force
        #[arg(short = 'y', long = "yes")]
        yes: bool,

        /// Explain what is being done
        #[arg(short = 'v', long)]
        verbose: bool,

        /// Exclude paths matching regex pattern
        #[arg(short = 'e', long)]
        exclude: Option<Vec<String>>,

        /// Enable inline TUI mode (classic 3-line display)
        #[arg(short, long)]
        tui: bool,

        /// Run in dry-run mode (no changes)
        #[arg(short = 'n', long)]
        dry_run: bool,

        /// Hidden test mode for simulation
        #[arg(long, hide = true, value_parser = parse_test_mode)]
        test_mode: Option<TestMode>,

        /// Verify file integrity after move
        #[arg(short = 'V', long, default_value_t = false)]
        verify: bool,

        /// Resume interrupted move (cross-device fallback only)
        #[arg(short = 'C', long, default_value_t = false)]
        resume: bool,

        /// Use strict hash verification for resume
        #[arg(short = 's', long, default_value_t = false)]
        strict: bool,

        /// Append data to existing file (ignores mtime, checks size only)
        #[arg(short = 'a', long, default_value_t = false)]
        append: bool,

        /// Sync data to disk after move (fsync, cross-device only)
        #[arg(long, default_value_t = false)]
        sync: bool,
    },

    /// Check for updates and self-update
    Update,

    #[command(name = "__complete-remote", hide = true)]
    CompleteRemote { partial: String },

    /// Generate shell completions
    Completions {
        /// Shell to generate completions for
        shell: clap_complete::Shell,
    },

    /// Remove files or directories
    Remove {
        /// Files or directories to remove
        #[arg(required = true)]
        paths: Vec<PathBuf>,

        /// Recursively remove directories (like rm -r)
        #[arg(short, long)]
        recursive: bool,

        /// Force removal without confirmation (like rm -f)
        #[arg(short = 'f', long)]
        force: bool,

        /// Skip confirmation prompt
        #[arg(short = 'y', long = "yes")]
        yes: bool,

        /// Interactively prompt before removal
        #[arg(short = 'i', long)]
        interactive: bool,

        /// Explain what is being done
        #[arg(short = 'v', long)]
        verbose: bool,

        /// Remove empty directories (like rmdir)
        #[arg(short = 'd', long)]
        dir: bool,

        /// Exclude files/directories that match these regex patterns
        #[arg(short = 'e', long, value_name = "PATTERN", value_delimiter = ',')]
        exclude: Option<Vec<String>>,

        /// Enable inline TUI mode (classic 3-line display)
        #[arg(short, long)]
        tui: bool,

        /// Run in dry-run mode (no changes)
        #[arg(short = 'n', long)]
        dry_run: bool,

        /// Hidden test mode for simulation
        #[arg(long, hide = true, value_parser = parse_test_mode)]
        test_mode: Option<TestMode>,
    },
}

#[derive(Debug, Clone)]
pub enum TestMode {
    Delay(u64),      // Milliseconds delay
    SpeedLimit(u64), // Bytes per second
    None,
}

impl Commands {
    pub fn get_test_mode(&self) -> TestMode {
        match self {
            Commands::Copy { test_mode, .. }
            | Commands::Move { test_mode, .. }
            | Commands::Remove { test_mode, .. } => test_mode.clone().unwrap_or(TestMode::None),
            _ => TestMode::None,
        }
    }

    pub fn compile_excludes(&self) -> Result<Vec<regex::Regex>, regex::Error> {
        let patterns = match self {
            Commands::Copy { exclude, .. }
            | Commands::Move { exclude, .. }
            | Commands::Remove { exclude, .. } => exclude.as_ref(),
            _ => None,
        };

        if let Some(patterns) = patterns {
            patterns.iter().map(|p| regex::Regex::new(p)).collect()
        } else {
            Ok(Vec::new())
        }
    }

    pub fn is_yes(&self) -> bool {
        match self {
            Commands::Copy { yes, .. }
            | Commands::Move { yes, .. }
            | Commands::Remove { yes, .. } => *yes,
            _ => false,
        }
    }

    pub fn should_prompt_for_overwrite(&self) -> bool {
        match self {
            Commands::Copy { force, yes, .. } | Commands::Move { force, yes, .. } => {
                *force && !*yes
            }
            Commands::Remove {
                force, interactive, ..
            } => !*force && *interactive,
            Commands::Init { .. }
            | Commands::Update
            | Commands::Completions { .. }
            | Commands::CompleteRemote { .. } => false,
        }
    }

    pub fn is_tui_mode(&self) -> bool {
        match self {
            Commands::Copy { tui, .. }
            | Commands::Move { tui, .. }
            | Commands::Remove { tui, .. } => *tui,
            _ => false,
        }
    }

    pub fn is_dry_run(&self) -> bool {
        match self {
            Commands::Copy { dry_run, .. }
            | Commands::Move { dry_run, .. }
            | Commands::Remove { dry_run, .. } => *dry_run,
            _ => false,
        }
    }

    pub fn get_sources_and_dest(&self) -> std::result::Result<(&[PathBuf], &PathBuf), String> {
        match self {
            Commands::Copy { paths, .. } | Commands::Move { paths, .. } => {
                let (dest, sources) = paths
                    .split_last()
                    .ok_or_else(|| "missing source/destination arguments".to_string())?;
                Ok((sources, dest))
            }
            _ => Err("command does not have source/destination structure".to_string()),
        }
    }

    pub fn is_verify(&self) -> bool {
        match self {
            Commands::Copy { verify, .. } | Commands::Move { verify, .. } => *verify,
            _ => false,
        }
    }

    pub fn is_resume(&self) -> bool {
        match self {
            Commands::Copy { resume, .. } | Commands::Move { resume, .. } => *resume,
            _ => false,
        }
    }

    pub fn is_strict(&self) -> bool {
        match self {
            Commands::Copy { strict, .. } | Commands::Move { strict, .. } => *strict,
            _ => false,
        }
    }

    pub fn is_append(&self) -> bool {
        match self {
            Commands::Copy { append, .. } | Commands::Move { append, .. } => *append,
            _ => false,
        }
    }

    pub fn is_sync(&self) -> bool {
        match self {
            Commands::Copy { sync, .. } | Commands::Move { sync, .. } => *sync,
            _ => false,
        }
    }

    pub fn get_reflink_mode(&self) -> Option<String> {
        match self {
            Commands::Copy { reflink, .. } => reflink.clone(),
            _ => None,
        }
    }

    pub fn get_sparse_mode(&self) -> Option<String> {
        match self {
            Commands::Copy { sparse, .. } => sparse.clone(),
            _ => None,
        }
    }

    pub fn get_parallel(&self) -> Option<usize> {
        match self {
            Commands::Copy { parallel, .. } => *parallel,
            _ => None,
        }
    }

    pub fn is_recursive(&self) -> bool {
        match self {
            Commands::Copy { recursive, .. }
            | Commands::Move { recursive, .. }
            | Commands::Remove { recursive, .. } => *recursive,
            _ => false,
        }
    }

    pub fn is_preserve(&self) -> bool {
        match self {
            Commands::Copy { preserve, .. } | Commands::Move { preserve, .. } => *preserve,
            _ => false,
        }
    }

    pub fn is_force(&self) -> bool {
        match self {
            Commands::Copy { force, .. }
            | Commands::Move { force, .. }
            | Commands::Remove { force, .. } => *force,
            _ => false,
        }
    }

    pub fn is_interactive(&self) -> bool {
        match self {
            Commands::Remove { interactive, .. } => *interactive,
            _ => false,
        }
    }

    pub fn is_verbose(&self) -> bool {
        match self {
            Commands::Copy { verbose, .. }
            | Commands::Move { verbose, .. }
            | Commands::Remove { verbose, .. } => *verbose,
            _ => false,
        }
    }

    pub fn is_dir_only(&self) -> bool {
        match self {
            Commands::Remove { dir, .. } => *dir,
            _ => false,
        }
    }

    pub fn get_remove_paths(&self) -> std::result::Result<&[PathBuf], String> {
        match self {
            Commands::Remove { paths, .. } => Ok(paths),
            _ => Err("command does not support remove paths".to_string()),
        }
    }
}

pub fn parse_args() -> Cli {
    Cli::parse()
}

fn parse_test_mode(s: &str) -> Result<TestMode, String> {
    if s == "none" {
        return Ok(TestMode::None);
    }
    let parts: Vec<&str> = s.split(':').collect();
    if parts.len() == 2 {
        match (parts[0], parts[1].parse::<u64>()) {
            ("delay", Ok(ms)) => Ok(TestMode::Delay(ms)),
            ("speed_limit", Ok(bps)) => Ok(TestMode::SpeedLimit(bps)),
            _ => Err(format!("Invalid test mode format: {}", s)),
        }
    } else {
        Err(format!(
            "Invalid test mode '{}'. Expected: none, delay:<ms>, or speed_limit:<bps>",
            s
        ))
    }
}

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

    #[test]
    fn test_parse_test_mode_delay() {
        match parse_test_mode("delay:100").unwrap() {
            TestMode::Delay(ms) => assert_eq!(ms, 100),
            _ => panic!("Expected Delay"),
        }
    }

    #[test]
    fn test_parse_test_mode_speed_limit() {
        match parse_test_mode("speed_limit:1048576").unwrap() {
            TestMode::SpeedLimit(bps) => assert_eq!(bps, 1048576),
            _ => panic!("Expected SpeedLimit"),
        }
    }

    #[test]
    fn test_parse_test_mode_none() {
        match parse_test_mode("none").unwrap() {
            TestMode::None => {}
            _ => panic!("Expected None"),
        }
    }

    #[test]
    fn test_parse_test_mode_invalid() {
        assert!(parse_test_mode("invalid:abc").is_err());
    }

    #[test]
    fn test_commands_copy_accessors() {
        let cmd = Commands::Copy {
            paths: vec![PathBuf::from("src"), PathBuf::from("dst")],
            recursive: true,
            preserve: true,
            force: true,
            yes: false,
            verbose: true,
            exclude: Some(vec!["*.log".to_string()]),
            tui: false,
            dry_run: true,
            test_mode: None,
            verify: true,
            resume: true,
            strict: true,
            append: false,
            sync: false,
            reflink: Some("auto".to_string()),
            sparse: None,
            parallel: Some(4),
        };

        assert!(cmd.is_recursive());
        assert!(cmd.is_preserve());
        assert!(cmd.is_force());
        assert!(!cmd.is_yes());
        assert!(cmd.is_verbose());
        assert!(cmd.is_dry_run());
        assert!(!cmd.is_tui_mode());
        assert!(cmd.is_verify());
        assert!(cmd.is_resume());
        assert!(cmd.is_strict());
        assert!(!cmd.is_append());
        assert!(!cmd.is_sync());
        assert_eq!(cmd.get_reflink_mode(), Some("auto".to_string()));
        assert_eq!(cmd.get_sparse_mode(), None);
        assert_eq!(cmd.get_parallel(), Some(4));
        assert!(cmd.should_prompt_for_overwrite());
    }

    #[test]
    fn test_commands_get_sources_and_dest() {
        let cmd = Commands::Copy {
            paths: vec![
                PathBuf::from("a"),
                PathBuf::from("b"),
                PathBuf::from("dest"),
            ],
            recursive: false,
            preserve: false,
            force: false,
            yes: false,
            verbose: false,
            exclude: None,
            tui: false,
            dry_run: false,
            test_mode: None,
            verify: false,
            resume: false,
            strict: false,
            append: false,
            sync: false,
            reflink: None,
            sparse: None,
            parallel: None,
        };

        let (sources, dest) = cmd.get_sources_and_dest().unwrap();
        assert_eq!(sources.len(), 2);
        assert_eq!(dest, &PathBuf::from("dest"));
    }

    #[test]
    fn test_commands_remove_accessors() {
        let cmd = Commands::Remove {
            paths: vec![PathBuf::from("file.txt")],
            recursive: false,
            force: true,
            yes: false,
            interactive: true,
            verbose: false,
            dir: true,
            exclude: None,
            tui: false,
            dry_run: false,
            test_mode: None,
        };

        assert!(cmd.is_force());
        assert!(cmd.is_interactive());
        assert!(cmd.is_dir_only());
        assert!(!cmd.is_recursive());
        let paths = cmd.get_remove_paths().unwrap();
        assert_eq!(paths.len(), 1);
    }

    #[test]
    fn test_commands_non_file_defaults() {
        let cmd = Commands::Update;
        assert!(!cmd.is_recursive());
        assert!(!cmd.is_force());
        assert!(!cmd.is_preserve());
        assert!(!cmd.is_verify());
        assert!(!cmd.is_dry_run());
        assert!(!cmd.is_verbose());
        assert_eq!(cmd.get_parallel(), None);
    }
}
