use crate::{
    config::{UpdateRegistry, WasmerConfig, WasmerEnv},
    VERSION,
};
use anyhow::{Context, Result};
use clap::Parser;
use std::str::ParseBoolError;

use super::AsyncCliCommand;

#[derive(Debug, Parser)]
/// The options for the `wasmer config` subcommand: `wasmer config get --OPTION` or `wasmer config set [FLAG]`
pub struct Config {
    #[clap(flatten)]
    env: WasmerEnv,

    #[clap(flatten)]
    flags: Flags,
    /// Subcommand for `wasmer config get | set`
    #[clap(subcommand)]
    set: Option<GetOrSet>,
}

/// Normal configuration
#[derive(Debug, Parser)]
pub struct Flags {
    /// Print the installation prefix.
    #[clap(long, conflicts_with = "pkg_config")]
    prefix: bool,

    /// Directory containing Wasmer executables.
    #[clap(long, conflicts_with = "pkg_config")]
    bindir: bool,

    /// Directory containing Wasmer headers.
    #[clap(long, conflicts_with = "pkg_config")]
    includedir: bool,

    /// Directory containing Wasmer libraries.
    #[clap(long, conflicts_with = "pkg_config")]
    libdir: bool,

    /// Libraries needed to link against Wasmer components.
    #[clap(long, conflicts_with = "pkg_config")]
    libs: bool,

    /// C compiler flags for files that include Wasmer headers.
    #[clap(long, conflicts_with = "pkg_config")]
    cflags: bool,

    /// Print the path to the wasmer configuration file where all settings are stored
    #[clap(long, conflicts_with = "pkg_config")]
    config_path: bool,

    /// Outputs the necessary details for compiling
    /// and linking a program to Wasmer, using the `pkg-config` format.
    #[clap(long)]
    pkg_config: bool,
}

/// Subcommand for `wasmer config set`
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
pub enum GetOrSet {
    /// `wasmer config get $KEY`
    #[clap(subcommand)]
    Get(RetrievableConfigField),
    /// `wasmer config set $KEY $VALUE`
    #[clap(subcommand)]
    Set(StorableConfigField),
}

/// Subcommand for `wasmer config get`
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
pub enum RetrievableConfigField {
    /// Print the registry URL of the currently active registry
    #[clap(name = "registry.url")]
    RegistryUrl,
    /// Print the token for the currently active registry or nothing if not logged in
    #[clap(name = "registry.token")]
    RegistryToken,
    /// Print whether telemetry is currently enabled
    #[clap(name = "telemetry.enabled")]
    TelemetryEnabled,
    /// Print whether update notifications are enabled
    #[clap(name = "update-notifications.enabled")]
    UpdateNotificationsEnabled,
    /// Print the proxy URL
    #[clap(name = "proxy.url")]
    ProxyUrl,
}

/// Setting that can be stored in the wasmer config
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
pub enum StorableConfigField {
    /// Set the registry URL of the currently active registry
    #[clap(name = "registry.url")]
    RegistryUrl(SetRegistryUrl),
    /// Set the token for the currently active registry or nothing if not logged in
    #[clap(name = "registry.token")]
    RegistryToken(SetRegistryToken),
    /// Set whether telemetry is currently enabled
    #[clap(name = "telemetry.enabled")]
    TelemetryEnabled(SetTelemetryEnabled),
    /// Set whether update notifications are enabled
    #[clap(name = "update-notifications.enabled")]
    UpdateNotificationsEnabled(SetUpdateNotificationsEnabled),
    /// Set the active proxy URL
    #[clap(name = "proxy.url")]
    ProxyUrl(SetProxyUrl),
}

/// Set the current active registry URL
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
pub struct SetRegistryUrl {
    /// Url of the registry
    #[clap(name = "URL")]
    pub url: String,
}

/// Set or change the token for the current active registry
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
pub struct SetRegistryToken {
    /// Token to set
    #[clap(name = "TOKEN")]
    pub token: String,
}

/// Set if update notifications are enabled
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
pub struct SetUpdateNotificationsEnabled {
    /// Whether to enable update notifications
    ///
    /// ("true" | "false")
    #[clap(name = "ENABLED")]
    pub enabled: BoolString,
}

/// "true" or "false" for handling input in the CLI
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct BoolString(pub bool);

impl std::str::FromStr for BoolString {
    type Err = ParseBoolError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        Ok(Self(bool::from_str(s)?))
    }
}

/// Set if telemetry is enabled
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
pub struct SetTelemetryEnabled {
    /// Whether to enable telemetry
    ///
    /// ("true" | "false")
    #[clap(name = "ENABLED")]
    pub enabled: BoolString,
}

/// Set if a proxy URL should be used
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Parser)]
pub struct SetProxyUrl {
    /// Set if a proxy URL should be used (empty = unset proxy)
    #[clap(name = "URL")]
    pub url: String,
}

#[async_trait::async_trait]
impl AsyncCliCommand for Config {
    type Output = ();

    /// Runs logic for the `config` subcommand
    async fn run_async(self) -> Result<Self::Output, anyhow::Error> {
        self.inner_execute()
            .await
            .context("failed to retrieve the wasmer config".to_string())
    }
}

impl Config {
    async fn inner_execute(&self) -> Result<()> {
        if let Some(s) = self.set.as_ref() {
            return s.execute(&self.env).await;
        }

        let flags = &self.flags;

        let prefix = self.env.dir();

        let prefixdir = prefix.display().to_string();
        let bindir = prefix.join("bin").display().to_string();
        let includedir = prefix.join("include").display().to_string();
        let libdir = prefix.join("lib").display().to_string();
        let cflags = format!("-I{includedir}");
        let libs = format!("-L{libdir} -lwasmer");

        if flags.pkg_config {
            println!("prefix={prefixdir}");
            println!("exec_prefix={bindir}");
            println!("includedir={includedir}");
            println!("libdir={libdir}");
            println!();
            println!("Name: wasmer");
            println!("Description: The Wasmer library for running WebAssembly");
            println!("Version: {VERSION}");
            println!("Cflags: {cflags}");
            println!("Libs: {libs}");
            return Ok(());
        }

        if flags.prefix {
            println!("{prefixdir}");
        }
        if flags.bindir {
            println!("{bindir}");
        }
        if flags.includedir {
            println!("{includedir}");
        }
        if flags.libdir {
            println!("{libdir}");
        }
        if flags.libs {
            println!("{libs}");
        }
        if flags.cflags {
            println!("{cflags}");
        }

        if flags.config_path {
            let path = WasmerConfig::get_file_location(self.env.dir());
            println!("{}", path.display());
        }

        Ok(())
    }
}

impl GetOrSet {
    async fn execute(&self, env: &WasmerEnv) -> Result<()> {
        let config_file = WasmerConfig::get_file_location(env.dir());
        let mut config = env.config()?;

        match self {
            GetOrSet::Get(g) => match g {
                RetrievableConfigField::RegistryUrl => {
                    println!("{}", config.registry.get_current_registry());
                }
                RetrievableConfigField::RegistryToken => {
                    if let Some(s) = config
                        .registry
                        .get_login_token_for_registry(&config.registry.get_current_registry())
                    {
                        println!("{s}");
                    }
                }
                RetrievableConfigField::TelemetryEnabled => {
                    println!("{:?}", config.telemetry_enabled);
                }
                RetrievableConfigField::UpdateNotificationsEnabled => {
                    println!("{:?}", config.update_notifications_enabled);
                }
                RetrievableConfigField::ProxyUrl => {
                    if let Some(s) = config.proxy.url.as_ref() {
                        println!("{s}");
                    } else {
                        println!("none");
                    }
                }
            },
            GetOrSet::Set(s) => {
                match s {
                    StorableConfigField::RegistryUrl(s) => {
                        config.registry.set_current_registry(&s.url).await;
                        let current_registry = config.registry.get_current_registry();
                        if let Ok(client) = env.client() {
                            if let Some(u) =
                                wasmer_backend_api::query::current_user(&client).await?
                            {
                                println!(
                                "Successfully logged into registry {current_registry:?} as user {u:?}"
                            );
                            }
                        }
                    }
                    StorableConfigField::RegistryToken(t) => {
                        config.registry.set_login_token_for_registry(
                            &config.registry.get_current_registry(),
                            &t.token,
                            UpdateRegistry::LeaveAsIs,
                        );
                    }
                    StorableConfigField::TelemetryEnabled(t) => {
                        config.telemetry_enabled = t.enabled.0;
                    }
                    StorableConfigField::ProxyUrl(p) => {
                        if p.url == "none" || p.url.is_empty() {
                            config.proxy.url = None;
                        } else {
                            config.proxy.url = Some(p.url.clone());
                        }
                    }
                    StorableConfigField::UpdateNotificationsEnabled(u) => {
                        config.update_notifications_enabled = u.enabled.0;
                    }
                }
                config
                    .save(config_file)
                    .with_context(|| anyhow::anyhow!("could not save config file"))?;
            }
        }
        Ok(())
    }
}
