use core::panic;
use std::{fs::create_dir_all, str::FromStr};

use tauri_bundler::{BundleSettings, PackageSettings, SettingsBuilder};

use super::*;
use crate::{build_desktop, cfg::ConfigOptsBundle};

/// Bundle the Rust desktop app and all of its assets
#[derive(Clone, Debug, Parser)]
#[clap(name = "bundle")]
pub struct Bundle {
    #[clap(long)]
    pub package: Option<Vec<String>>,
    #[clap(flatten)]
    pub build: ConfigOptsBundle,
}

#[derive(Clone, Debug)]
pub enum PackageType {
    MacOsBundle,
    IosBundle,
    WindowsMsi,
    Deb,
    Rpm,
    AppImage,
    Dmg,
    Updater,
}

impl FromStr for PackageType {
    type Err = String;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "macos" => Ok(PackageType::MacOsBundle),
            "ios" => Ok(PackageType::IosBundle),
            "msi" => Ok(PackageType::WindowsMsi),
            "deb" => Ok(PackageType::Deb),
            "rpm" => Ok(PackageType::Rpm),
            "appimage" => Ok(PackageType::AppImage),
            "dmg" => Ok(PackageType::Dmg),
            _ => Err(format!("{} is not a valid package type", s)),
        }
    }
}

impl From<PackageType> for tauri_bundler::PackageType {
    fn from(val: PackageType) -> Self {
        match val {
            PackageType::MacOsBundle => tauri_bundler::PackageType::MacOsBundle,
            PackageType::IosBundle => tauri_bundler::PackageType::IosBundle,
            PackageType::WindowsMsi => tauri_bundler::PackageType::WindowsMsi,
            PackageType::Deb => tauri_bundler::PackageType::Deb,
            PackageType::Rpm => tauri_bundler::PackageType::Rpm,
            PackageType::AppImage => tauri_bundler::PackageType::AppImage,
            PackageType::Dmg => tauri_bundler::PackageType::Dmg,
            PackageType::Updater => tauri_bundler::PackageType::Updater,
        }
    }
}

impl Bundle {
    pub fn bundle(self, bin: Option<PathBuf>) -> Result<()> {
        let mut crate_config = crate::CrateConfig::new(bin)?;

        // change the release state.
        crate_config.with_release(self.build.release);
        crate_config.with_verbose(self.build.verbose);

        if self.build.example.is_some() {
            crate_config.as_example(self.build.example.unwrap());
        }

        if self.build.profile.is_some() {
            crate_config.set_profile(self.build.profile.unwrap());
        }

        // build the desktop app
        build_desktop(&crate_config, false)?;

        // copy the binary to the out dir
        let package = crate_config.manifest.package.unwrap();

        let mut name: PathBuf = match &crate_config.executable {
            crate::ExecutableType::Binary(name)
            | crate::ExecutableType::Lib(name)
            | crate::ExecutableType::Example(name) => name,
        }
        .into();
        if cfg!(windows) {
            name.set_extension("exe");
        }

        // bundle the app
        let binaries = vec![
            tauri_bundler::BundleBinary::new(name.display().to_string(), true)
                .set_src_path(Some(crate_config.crate_dir.display().to_string())),
        ];

        let mut bundle_settings: BundleSettings = crate_config.dioxus_config.bundle.clone().into();
        if cfg!(windows) {
            let windows_icon_override = crate_config
                .dioxus_config
                .bundle
                .windows
                .as_ref()
                .map(|w| &w.icon_path);
            if windows_icon_override.is_none() {
                let icon_path = bundle_settings
                    .icon
                    .as_ref()
                    .and_then(|icons| icons.first());
                let icon_path = if let Some(icon_path) = icon_path {
                    icon_path.into()
                } else {
                    let path = PathBuf::from("./icons/icon.ico");
                    // create the icon if it doesn't exist
                    if !path.exists() {
                        create_dir_all(path.parent().unwrap()).unwrap();
                        let mut file = File::create(&path).unwrap();
                        file.write_all(include_bytes!("../assets/icon.ico"))
                            .unwrap();
                    }
                    path
                };
                bundle_settings.windows.icon_path = icon_path;
            }
        }

        let mut settings = SettingsBuilder::new()
            .project_out_directory(crate_config.out_dir)
            .package_settings(PackageSettings {
                product_name: crate_config.dioxus_config.application.name.clone(),
                version: package.version,
                description: package.description.unwrap_or_default(),
                homepage: package.homepage,
                authors: Some(package.authors),
                default_run: Some(crate_config.dioxus_config.application.name.clone()),
            })
            .binaries(binaries)
            .bundle_settings(bundle_settings);
        if let Some(packages) = self.package {
            settings = settings.package_types(
                packages
                    .into_iter()
                    .map(|p| p.parse::<PackageType>().unwrap().into())
                    .collect(),
            );
        }
        let settings = settings.build();

        // on macos we need to set CI=true (https://github.com/tauri-apps/tauri/issues/2567)
        #[cfg(target_os = "macos")]
        std::env::set_var("CI", "true");

        tauri_bundler::bundle::bundle_project(settings.unwrap()).unwrap_or_else(|err|{
            #[cfg(target_os = "macos")]
            panic!("Failed to bundle project: {}\nMake sure you have automation enabled in your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208) and full disk access enabled for your terminal (https://github.com/tauri-apps/tauri/issues/3055#issuecomment-1624389208)", err);
            #[cfg(not(target_os = "macos"))]
            panic!("Failed to bundle project: {}", err);
        });

        Ok(())
    }
}
