use std::env;
use std::path::PathBuf;

use crate::config::{ConfigCommand, ConfigOverrides};
use crate::terminal::{LaunchCommand, LaunchConfig};

#[derive(Debug, Clone)]
pub struct Cli {
    pub action: CliAction,
}

#[derive(Debug, Clone)]
pub enum CliAction {
    Run {
        launch: LaunchConfig,
        config_path: Option<PathBuf>,
        overrides: Box<ConfigOverrides>,
    },
    Config(ConfigCommand),
    InstallDesktop,
    UninstallDesktop,
    ShowHelp,
    ShowVersion,
}

impl Cli {
    pub fn parse() -> Result<Self, String> {
        Self::parse_from(env::args().skip(1))
    }

    fn parse_from(args: impl IntoIterator<Item = String>) -> Result<Self, String> {
        let args: Vec<String> = args.into_iter().collect();
        if args.first().is_some_and(|arg| arg == "config") {
            return Ok(Self {
                action: CliAction::Config(parse_config_command(&args[1..])?),
            });
        }

        let mut args = args.into_iter().peekable();
        let mut working_directory = None;
        let mut config_path = None;
        let mut overrides = ConfigOverrides::default();

        while let Some(arg) = args.next() {
            match arg.as_str() {
                "-h" | "--help" => {
                    return Ok(Self {
                        action: CliAction::ShowHelp,
                    });
                }
                "-V" | "--version" => {
                    return Ok(Self {
                        action: CliAction::ShowVersion,
                    });
                }
                "--install" | "---install" | "install" => {
                    return Ok(Self {
                        action: CliAction::InstallDesktop,
                    });
                }
                "--uninstall" | "---uninstall" | "uninstall" => {
                    return Ok(Self {
                        action: CliAction::UninstallDesktop,
                    });
                }
                "--working-directory" => {
                    let Some(value) = args.next() else {
                        return Err("--working-directory requires a path".to_string());
                    };
                    working_directory = Some(PathBuf::from(value));
                }
                "--config" => {
                    let Some(value) = args.next() else {
                        return Err("--config requires a path".to_string());
                    };
                    config_path = Some(PathBuf::from(value));
                }
                "--theme" => {
                    overrides.theme_name = Some(required_value(&arg, args.next())?);
                }
                "--renderer" => {
                    overrides.renderer = Some(required_value(&arg, args.next())?);
                }
                "--gpu" | "--gpu-acceleration" => {
                    overrides.renderer = Some("auto".to_string());
                }
                "--no-gpu" | "--cpu" | "--software-renderer" => {
                    overrides.renderer = Some("cairo".to_string());
                }
                "--gpu-mode" => {
                    overrides.renderer = Some(required_gpu_mode(&arg, args.next())?);
                }
                "--opacity" | "--window-opacity" | "--total-opacity" => {
                    overrides.window_opacity = Some(parse_float(&arg, args.next())?);
                }
                "--font" => {
                    overrides.font = Some(required_value(&arg, args.next())?);
                }
                "--background-image" => {
                    overrides.background_image =
                        Some(PathBuf::from(required_value(&arg, args.next())?));
                }
                "--background-image-opacity" => {
                    overrides.background_image_opacity = Some(parse_float(&arg, args.next())?);
                }
                "--background-opacity" => {
                    overrides.terminal_opacity = Some(parse_float(&arg, args.next())?);
                }
                "--overlay-color" => {
                    overrides.overlay_color = Some(required_value(&arg, args.next())?);
                }
                "--overlay-opacity" => {
                    overrides.overlay_opacity = Some(parse_float(&arg, args.next())?);
                }
                "--random-overlay" => {
                    overrides.random_overlay = Some(true);
                }
                "--no-random-overlay" => {
                    overrides.random_overlay = Some(false);
                }
                "--hide-titlebar" | "--hide-topbar" | "--no-titlebar" | "--no-topbar" => {
                    overrides.decorated = Some(false);
                }
                "--show-titlebar" | "--show-topbar" => {
                    overrides.decorated = Some(true);
                }
                "-e" | "--command" => {
                    let Some(command) = args.next() else {
                        return Err(format!("{arg} requires a command string"));
                    };
                    return Ok(Self::run(
                        working_directory,
                        LaunchCommand::Shell(command),
                        config_path,
                        overrides,
                    ));
                }
                "--" => {
                    let command: Vec<String> = args.collect();
                    if command.is_empty() {
                        return Err("-- requires a command".to_string());
                    }
                    return Ok(Self::run(
                        working_directory,
                        LaunchCommand::Argv(command),
                        config_path,
                        overrides,
                    ));
                }
                _ if arg.starts_with("--working-directory=") => {
                    let (_, value) = arg.split_once('=').expect("prefix checked above");
                    if value.is_empty() {
                        return Err("--working-directory requires a path".to_string());
                    }
                    working_directory = Some(PathBuf::from(value));
                }
                _ if arg.starts_with("--config=") => {
                    config_path = Some(PathBuf::from(split_value(&arg)?));
                }
                _ if arg.starts_with("--theme=") => {
                    overrides.theme_name = Some(split_value(&arg)?);
                }
                _ if arg.starts_with("--renderer=") => {
                    overrides.renderer = Some(split_value(&arg)?);
                }
                _ if arg.starts_with("--gpu-mode=") => {
                    overrides.renderer = Some(split_gpu_mode(&arg)?);
                }
                _ if arg.starts_with("--opacity=")
                    || arg.starts_with("--window-opacity=")
                    || arg.starts_with("--total-opacity=") =>
                {
                    overrides.window_opacity = Some(parse_split_float(&arg)?);
                }
                _ if arg.starts_with("--font=") => {
                    overrides.font = Some(split_value(&arg)?);
                }
                _ if arg.starts_with("--background-image=") => {
                    overrides.background_image = Some(PathBuf::from(split_value(&arg)?));
                }
                _ if arg.starts_with("--background-image-opacity=") => {
                    overrides.background_image_opacity = Some(parse_split_float(&arg)?);
                }
                _ if arg.starts_with("--background-opacity=") => {
                    overrides.terminal_opacity = Some(parse_split_float(&arg)?);
                }
                _ if arg.starts_with("--overlay-color=") => {
                    overrides.overlay_color = Some(split_value(&arg)?);
                }
                _ if arg.starts_with("--overlay-opacity=") => {
                    overrides.overlay_opacity = Some(parse_split_float(&arg)?);
                }
                _ => {
                    return Err(format!(
                        "unknown option '{arg}'\n\n{}",
                        Self::short_help_text()
                    ));
                }
            }
        }

        Ok(Self::run(
            working_directory,
            LaunchCommand::DefaultShell,
            config_path,
            overrides,
        ))
    }

    fn run(
        working_directory: Option<PathBuf>,
        command: LaunchCommand,
        config_path: Option<PathBuf>,
        overrides: ConfigOverrides,
    ) -> Self {
        Self {
            action: CliAction::Run {
                launch: LaunchConfig {
                    command,
                    working_directory,
                },
                config_path,
                overrides: Box::new(overrides),
            },
        }
    }

    pub fn help_text() -> &'static str {
        r#"Lios Terminal

Usage:
  lios [OPTIONS]
  lios [OPTIONS] -- COMMAND [ARGUMENTS...]
  lios config COMMAND [ARGS...]

Options:
  -h, --help                         Show this help text
  -V, --version                      Show the application version
      --install, ---install          Install a desktop launcher for this binary
      --uninstall, ---uninstall      Remove the desktop launcher
      --config PATH                  Load a TOML config file
      --working-directory PATH       Start the child process in PATH
  -e, --command COMMAND              Run COMMAND through /bin/sh -lc
      --gpu                          Enable GTK GPU renderer auto-selection
      --no-gpu                       Force the reliable Cairo software renderer
      --gpu-mode NAME                GPU mode: auto, gl, or vulkan
      --renderer NAME                GTK renderer: auto, gl, vulkan, cairo
      --opacity N                    Set full-window opacity, 0.0 to 1.0
      --theme NAME                   Use a built-in theme
      --font FONT                    Set the terminal font
      --background-image PATH        Draw an image behind the terminal
      --background-image-opacity N   Set image opacity, 0.0 to 1.0
      --background-opacity N         Set terminal shade opacity, 0.0 to 1.0
      --overlay-color COLOR          Tint the background, for example #7c3aed
      --overlay-opacity N            Set tint opacity, 0.0 to 1.0
      --random-overlay               Pick a new accent tint each launch
      --no-random-overlay            Disable random accent tint
      --hide-titlebar, --hide-topbar  Remove window decorations/topbar
      --show-titlebar, --show-topbar  Keep window decorations/topbar

Config Commands:
  lios config path
  lios config sample
  lios config init [--force] [--path PATH]
  lios config show [--path PATH]
  lios config set [--path PATH] KEY VALUE

Shortcuts: Ctrl+Shift+N opens another terminal window. Ctrl+Shift+Q closes the current window. Ctrl+Shift+, toggles preferences. Esc closes preferences when open.
Common config keys: gpu, gpu_mode, renderer, theme, font, opacity, background.image, background_opacity, background_image_opacity, overlay_color, overlay_opacity, random_overlay, topbar.
Built-in themes: xfce, xterm, green-on-black, white-on-black, dark-pastels, cyberpunk, matrix, ocean, ember, solarized-dark, solarized-light, black-on-white.
Without a command, the terminal starts your login shell from $SHELL or a safe fallback.
"#
    }

    fn short_help_text() -> &'static str {
        "Run 'lios --help' for usage."
    }
}

fn required_value(option: &str, value: Option<String>) -> Result<String, String> {
    value.ok_or_else(|| format!("{option} requires a value"))
}

fn split_value(option: &str) -> Result<String, String> {
    let (_, value) = option.split_once('=').expect("caller checked for '='");
    if value.is_empty() {
        Err(format!("{option} requires a value"))
    } else {
        Ok(value.to_string())
    }
}

fn required_gpu_mode(option: &str, value: Option<String>) -> Result<String, String> {
    let value = required_value(option, value)?;
    validate_gpu_mode(option, &value)?;
    Ok(value)
}

fn split_gpu_mode(option: &str) -> Result<String, String> {
    let value = split_value(option)?;
    validate_gpu_mode(option, &value)?;
    Ok(value)
}

fn validate_gpu_mode(option: &str, value: &str) -> Result<(), String> {
    match value.trim().to_ascii_lowercase().as_str() {
        "auto" | "default" | "gl" | "opengl" | "vulkan" | "vk" => Ok(()),
        _ => Err(format!("{option} must be auto, gl, or vulkan")),
    }
}

fn parse_float(option: &str, value: Option<String>) -> Result<f64, String> {
    let value = required_value(option, value)?;
    value
        .parse::<f64>()
        .map_err(|_| format!("{option} requires a number"))
}

fn parse_split_float(option: &str) -> Result<f64, String> {
    let value = split_value(option)?;
    value
        .parse::<f64>()
        .map_err(|_| format!("{option} requires a number"))
}

fn parse_config_command(args: &[String]) -> Result<ConfigCommand, String> {
    let Some(command) = args.first().map(String::as_str) else {
        return Err("missing config command\n\nRun 'lios --help' for usage.".to_string());
    };

    match command {
        "path" => Ok(ConfigCommand::Path),
        "sample" => Ok(ConfigCommand::Sample),
        "init" => {
            let (path, force, rest) = parse_config_options(&args[1..])?;
            if !rest.is_empty() {
                return Err(format!(
                    "unexpected argument '{}'
",
                    rest[0]
                ));
            }
            Ok(ConfigCommand::Init { path, force })
        }
        "show" => {
            let (path, _, rest) = parse_config_options(&args[1..])?;
            if !rest.is_empty() {
                return Err(format!(
                    "unexpected argument '{}'
",
                    rest[0]
                ));
            }
            Ok(ConfigCommand::Show { path })
        }
        "set" => {
            let (path, _, rest) = parse_config_options(&args[1..])?;
            if rest.len() != 2 {
                return Err("usage: lios config set [--path PATH] KEY VALUE".to_string());
            }
            Ok(ConfigCommand::Set {
                path,
                key: rest[0].clone(),
                value: rest[1].clone(),
            })
        }
        _ => Err(format!("unknown config command '{command}'")),
    }
}

fn parse_config_options(args: &[String]) -> Result<(Option<PathBuf>, bool, Vec<String>), String> {
    let mut path = None;
    let mut force = false;
    let mut rest = Vec::new();
    let mut index = 0;

    while index < args.len() {
        match args[index].as_str() {
            "--path" | "--config" => {
                index += 1;
                let Some(value) = args.get(index) else {
                    return Err("--path requires a value".to_string());
                };
                path = Some(PathBuf::from(value));
            }
            "--force" => force = true,
            _ if args[index].starts_with("--path=") || args[index].starts_with("--config=") => {
                path = Some(PathBuf::from(split_value(&args[index])?));
            }
            _ => rest.push(args[index].clone()),
        }
        index += 1;
    }

    Ok((path, force, rest))
}

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

    #[test]
    fn install_aliases_parse() {
        assert!(matches!(
            parse(["--install"]).action,
            CliAction::InstallDesktop
        ));
        assert!(matches!(
            parse(["---install"]).action,
            CliAction::InstallDesktop
        ));
        assert!(matches!(
            parse(["install"]).action,
            CliAction::InstallDesktop
        ));
    }

    #[test]
    fn uninstall_aliases_parse() {
        assert!(matches!(
            parse(["--uninstall"]).action,
            CliAction::UninstallDesktop
        ));
        assert!(matches!(
            parse(["---uninstall"]).action,
            CliAction::UninstallDesktop
        ));
        assert!(matches!(
            parse(["uninstall"]).action,
            CliAction::UninstallDesktop
        ));
    }

    #[test]
    fn opacity_flags_parse() {
        let action = parse(["--opacity", "0.82"]).action;
        let CliAction::Run { overrides, .. } = action else {
            panic!("expected run action");
        };
        assert_eq!(overrides.window_opacity, Some(0.82));

        let action = parse(["--total-opacity=0.64"]).action;
        let CliAction::Run { overrides, .. } = action else {
            panic!("expected run action");
        };
        assert_eq!(overrides.window_opacity, Some(0.64));
    }

    #[test]
    fn gpu_flags_parse_renderer_override() {
        let action = parse(["--gpu"]).action;
        let CliAction::Run { overrides, .. } = action else {
            panic!("expected run action");
        };
        assert_eq!(overrides.renderer, Some("auto".to_string()));

        let action = parse(["--gpu-mode=vulkan"]).action;
        let CliAction::Run { overrides, .. } = action else {
            panic!("expected run action");
        };
        assert_eq!(overrides.renderer, Some("vulkan".to_string()));

        let action = parse(["--no-gpu"]).action;
        let CliAction::Run { overrides, .. } = action else {
            panic!("expected run action");
        };
        assert_eq!(overrides.renderer, Some("cairo".to_string()));
    }

    #[test]
    fn gpu_mode_rejects_software_renderer() {
        assert!(parse_result(["--gpu-mode=cairo"]).is_err());
    }

    #[test]
    fn help_mentions_preferences_escape_shortcut() {
        assert!(Cli::help_text().contains("Esc closes preferences when open"));
    }

    #[test]
    fn background_opacity_flag_parses_terminal_shade() {
        let action = parse(["--background-opacity=0.42"]).action;
        let CliAction::Run { overrides, .. } = action else {
            panic!("expected run action");
        };
        assert_eq!(overrides.terminal_opacity, Some(0.42));
    }

    #[test]
    fn shell_command_mode_parses() {
        let action = parse(["--working-directory", "/tmp", "--command", "printf ok"]).action;
        let CliAction::Run { launch, .. } = action else {
            panic!("expected run action");
        };

        assert_eq!(launch.working_directory, Some(PathBuf::from("/tmp")));
        match launch.command {
            LaunchCommand::Shell(command) => assert_eq!(command, "printf ok"),
            _ => panic!("expected shell command"),
        }
    }

    #[test]
    fn argv_command_mode_parses() {
        let action = parse(["--", "printf", "ok"]).action;
        let CliAction::Run { launch, .. } = action else {
            panic!("expected run action");
        };

        match launch.command {
            LaunchCommand::Argv(argv) => assert_eq!(argv, vec!["printf", "ok"]),
            _ => panic!("expected argv command"),
        }
    }

    #[test]
    fn config_set_command_parses() {
        let action = parse([
            "config",
            "set",
            "--path",
            "/tmp/lios.toml",
            "opacity",
            "0.73",
        ])
        .action;
        let CliAction::Config(ConfigCommand::Set { path, key, value }) = action else {
            panic!("expected config set action");
        };

        assert_eq!(path, Some(PathBuf::from("/tmp/lios.toml")));
        assert_eq!(key, "opacity");
        assert_eq!(value, "0.73");
    }

    #[test]
    fn empty_argv_mode_errors() {
        assert!(parse_result(["--"]).is_err());
    }

    fn parse<const N: usize>(args: [&str; N]) -> Cli {
        parse_result(args).unwrap()
    }

    fn parse_result<const N: usize>(args: [&str; N]) -> Result<Cli, String> {
        Cli::parse_from(args.into_iter().map(str::to_string))
    }
}
