use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

/// A resolved shell executable and the flag used to pass a command string.
#[derive(Debug, Clone)]
pub struct ShellCommand {
    pub program: String,
    pub flag: String,
}

/// Supported shell types for executing commands.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum ShellType {
    #[serde(rename = "system_default")]
    SystemDefault,
    #[serde(rename = "bash")]
    Bash,
    #[serde(rename = "zsh")]
    Zsh,
    #[serde(rename = "fish")]
    Fish,
    #[serde(rename = "powershell")]
    PowerShell,
    #[serde(rename = "custom")]
    Custom(String),
}

impl ShellType {
    pub const fn executable(&self) -> Option<&str> {
        match self {
            ShellType::SystemDefault => None,
            ShellType::Bash => Some("bash"),
            ShellType::Zsh => Some("zsh"),
            ShellType::Fish => Some("fish"),
            ShellType::PowerShell => None,
            ShellType::Custom(path) => Some(path.as_str()),
        }
    }

    pub fn resolve_executable(&self) -> String {
        match self {
            ShellType::SystemDefault => std::env::var("SHELL").unwrap_or_else(|_| "sh".to_string()),
            ShellType::Bash => "bash".to_string(),
            ShellType::Zsh => "zsh".to_string(),
            ShellType::Fish => "fish".to_string(),
            ShellType::PowerShell => {
                #[cfg(windows)]
                {
                    "powershell.exe".to_string()
                }
                #[cfg(not(windows))]
                {
                    "pwsh".to_string()
                }
            }
            ShellType::Custom(path) => path.clone(),
        }
    }

    pub fn resolve_command(&self) -> ShellCommand {
        match self {
            ShellType::SystemDefault => {
                #[cfg(windows)]
                {
                    let comspec =
                        std::env::var("ComSpec").unwrap_or_else(|_| "cmd.exe".to_string());
                    ShellCommand {
                        program: comspec,
                        flag: "/C".to_string(),
                    }
                }
                #[cfg(not(windows))]
                {
                    let shell = std::env::var("SHELL").unwrap_or_else(|_| "sh".to_string());
                    ShellCommand {
                        program: shell,
                        flag: "-c".to_string(),
                    }
                }
            }
            ShellType::Bash | ShellType::Zsh | ShellType::Fish => ShellCommand {
                program: self.executable().unwrap().to_string(),
                flag: "-c".to_string(),
            },
            ShellType::PowerShell => {
                #[cfg(windows)]
                {
                    ShellCommand {
                        program: "powershell.exe".to_string(),
                        flag: "-Command".to_string(),
                    }
                }
                #[cfg(not(windows))]
                {
                    ShellCommand {
                        program: "pwsh".to_string(),
                        flag: "-Command".to_string(),
                    }
                }
            }
            ShellType::Custom(path) => {
                let lower = path.to_lowercase();
                // 用 ends_with 替代 contains，避免误配中间子串（如 /usr/bin/mycmd）
                let flag = if lower.ends_with("cmd") || lower.ends_with("cmd.exe") {
                    "/C"
                } else if lower.ends_with("powershell")
                    || lower.ends_with("pwsh")
                    || lower.ends_with("powershell.exe")
                {
                    "-Command"
                } else {
                    "-c"
                };
                ShellCommand {
                    program: path.clone(),
                    flag: flag.to_string(),
                }
            }
        }
    }

    pub fn label(&self) -> String {
        match self {
            ShellType::SystemDefault => "System Default".to_string(),
            ShellType::Bash => "bash".to_string(),
            ShellType::Zsh => "zsh".to_string(),
            ShellType::Fish => "fish".to_string(),
            ShellType::PowerShell => "PowerShell".to_string(),
            ShellType::Custom(path) => format!("custom: {}", path),
        }
    }

    pub fn builtin_variants() -> Vec<ShellType> {
        vec![
            ShellType::SystemDefault,
            ShellType::Bash,
            ShellType::Zsh,
            ShellType::Fish,
            ShellType::PowerShell,
        ]
    }
}

/// Execution mode for a command set.
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum ExecMode {
    #[serde(rename = "stop_on_error")]
    StopOnError,
    #[serde(rename = "continue_on_error")]
    ContinueOnError,
}

impl ExecMode {
    pub fn label(&self) -> &str {
        match self {
            ExecMode::StopOnError => "Stop on Error",
            ExecMode::ContinueOnError => "Continue on Error",
        }
    }
}

/// A template variable with a default value.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Variable {
    pub name: String,
    pub default_value: String,
}

/// A single shell command within a command set.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Command {
    pub position: usize,
    pub command: String,
}

/// A named collection of commands to execute.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct CommandSet {
    pub id: Uuid,
    pub name: String,
    pub group_id: Uuid,
    pub shell: ShellType,
    pub exec_mode: ExecMode,
    pub variables: Vec<Variable>,
    pub commands: Vec<Command>,
    /// Commands that run after all normal commands complete, regardless of
    /// success/failure/interrupt. Previous data deserializes with an empty vec.
    #[serde(default)]
    pub defer_commands: Vec<Command>,
    pub working_dir: Option<String>,
    pub created_at: DateTime<Utc>,
    pub updated_at: DateTime<Utc>,
}

impl CommandSet {
    pub fn new(name: String, group_id: Uuid) -> Self {
        let now = Utc::now();
        Self {
            id: Uuid::new_v4(),
            name,
            group_id,
            shell: ShellType::SystemDefault,
            exec_mode: ExecMode::StopOnError,
            variables: Vec::new(),
            commands: Vec::new(),
            defer_commands: Vec::new(),
            working_dir: None,
            created_at: now,
            updated_at: now,
        }
    }
}

/// A group (folder) that organises command sets.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Group {
    pub id: Uuid,
    pub name: String,
    pub sets: Vec<CommandSet>,
}

impl Group {
    pub fn new(name: String) -> Self {
        Self {
            id: Uuid::new_v4(),
            name,
            sets: Vec::new(),
        }
    }
}

/// Root data structure for the entire application.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct AppData {
    pub groups: Vec<Group>,
}

impl AppData {
    pub fn empty() -> Self {
        Self { groups: Vec::new() }
    }
}

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

    #[test]
    fn test_app_data_empty() {
        let data = AppData::empty();
        assert!(data.groups.is_empty());
    }

    #[test]
    fn test_create_group() {
        let group = Group::new("Deploy".to_string());
        assert_eq!(group.name, "Deploy");
        assert!(group.sets.is_empty());
    }

    #[test]
    fn test_create_command_set() {
        let group = Group::new("Deploy".to_string());
        let set = CommandSet::new("Deploy to Prod".to_string(), group.id);
        assert_eq!(set.name, "Deploy to Prod");
        assert_eq!(set.group_id, group.id);
        assert_eq!(set.shell, ShellType::SystemDefault);
        assert_eq!(set.exec_mode, ExecMode::StopOnError);
        assert!(set.variables.is_empty());
        assert!(set.commands.is_empty());
    }

    #[test]
    fn test_serde_roundtrip_app_data() {
        let mut group = Group::new("Test".to_string());
        let set = CommandSet::new("Test Set".to_string(), group.id);
        group.sets.push(set);
        let data = AppData {
            groups: vec![group],
        };
        let json = serde_json::to_string_pretty(&data).expect("serialize");
        let deserialized: AppData = serde_json::from_str(&json).expect("deserialize");
        assert_eq!(data, deserialized);
    }

    #[test]
    fn test_serde_roundtrip_shell_types() {
        for shell in &[
            ShellType::SystemDefault,
            ShellType::Bash,
            ShellType::Zsh,
            ShellType::Fish,
            ShellType::Custom("/usr/bin/zsh".to_string()),
        ] {
            let json = serde_json::to_string(shell).expect("serialize");
            let deserialized: ShellType = serde_json::from_str(&json).expect("deserialize");
            assert_eq!(*shell, deserialized);
        }
    }

    #[test]
    fn test_serde_roundtrip_exec_mode() {
        for mode in &[ExecMode::StopOnError, ExecMode::ContinueOnError] {
            let json = serde_json::to_string(mode).expect("serialize");
            let deserialized: ExecMode = serde_json::from_str(&json).expect("deserialize");
            assert_eq!(*mode, deserialized);
        }
    }

    #[test]
    fn test_shell_label() {
        assert_eq!(ShellType::Bash.label(), "bash");
        assert_eq!(ShellType::SystemDefault.label(), "System Default");
        assert_eq!(ShellType::Zsh.label(), "zsh");
    }

    #[test]
    fn test_exec_mode_label() {
        assert_eq!(ExecMode::StopOnError.label(), "Stop on Error");
        assert_eq!(ExecMode::ContinueOnError.label(), "Continue on Error");
    }

    #[test]
    fn test_builtin_variants_count() {
        assert_eq!(ShellType::builtin_variants().len(), 5);
    }

    #[test]
    fn test_command_set_new_sets_updated_at() {
        let group_id = Uuid::new_v4();
        let set = CommandSet::new("Test".to_string(), group_id);
        assert_eq!(set.created_at, set.updated_at);
        assert!(set.created_at <= Utc::now());
    }

    #[test]
    fn test_command_set_working_dir_defaults_to_none() {
        let group_id = Uuid::new_v4();
        let set = CommandSet::new("Test".to_string(), group_id);
        assert_eq!(set.working_dir, None);
    }

    #[test]
    fn test_command_set_defer_commands_defaults_empty() {
        let group_id = Uuid::new_v4();
        let set = CommandSet::new("Test".to_string(), group_id);
        assert!(set.defer_commands.is_empty());
    }

    #[test]
    fn test_command_set_defer_commands_serde_backwards_compat() {
        let json = r#"{"id":"00000000-0000-0000-0000-000000000001","name":"S","group_id":"00000000-0000-0000-0000-000000000002","shell":"bash","exec_mode":"stop_on_error","variables":[],"commands":[],"working_dir":null,"created_at":"2025-01-01T00:00:00Z","updated_at":"2025-01-01T00:00:00Z"}"#;
        let set: CommandSet = serde_json::from_str(json).unwrap();
        assert!(set.defer_commands.is_empty());
    }
}
