use std::path::PathBuf;

use ely_domain::{PluginId, PluginManifest, PluginPermission};
use gpui::{Context, PathPromptOptions, Window};

use crate::services::{
    plugin_package_store::PluginPackageStore,
    plugin_packages::{PluginPackageError, PluginPackageReader, VerifiedPluginPackage},
};

use super::{ElyShell, ShellState};

const PLUGIN_SETTINGS_URL: &str = "ely://settings/plugins";

#[derive(Clone, Debug)]
pub(super) struct PendingPluginInstall {
    package: VerifiedPluginPackage,
    high_risk_permissions: Vec<PluginPermission>,
}

#[derive(Clone, Debug)]
pub(super) struct PendingPluginUninstall {
    plugin_id: PluginId,
    plugin_name: String,
}

impl PendingPluginInstall {
    fn new(package: VerifiedPluginPackage, high_risk_permissions: Vec<PluginPermission>) -> Self {
        Self { package, high_risk_permissions }
    }

    pub(super) fn manifest(&self) -> &PluginManifest {
        self.package.manifest()
    }

    pub(super) fn high_risk_permissions(&self) -> &[PluginPermission] {
        &self.high_risk_permissions
    }

    pub(super) fn requires_high_risk_confirmation(&self) -> bool {
        !self.high_risk_permissions.is_empty()
    }
}

impl PendingPluginUninstall {
    fn new(plugin_id: PluginId, plugin_name: String) -> Self {
        Self { plugin_id, plugin_name }
    }

    pub(super) fn plugin_id(&self) -> &PluginId {
        &self.plugin_id
    }

    pub(super) fn plugin_name(&self) -> &str {
        &self.plugin_name
    }
}

impl ElyShell {
    pub(super) fn choose_plugin_package(&mut self, window: &mut Window, cx: &mut Context<Self>) {
        self.ensure_plugin_install_surface(window, cx);

        let prompt = cx.prompt_for_paths(PathPromptOptions {
            files: false,
            directories: true,
            multiple: false,
            prompt: Some("Select .rplug package".into()),
        });

        cx.spawn_in(window, async move |shell, window| {
            let selected_path = match prompt.await {
                Ok(Ok(Some(paths))) => paths.into_iter().next(),
                Ok(Ok(None)) => None,
                Ok(Err(error)) => {
                    _ = shell.update_in(window, |shell, _, cx| {
                        shell.plugin_install_error = Some(error.to_string());
                        cx.notify();
                    });
                    return;
                }
                Err(error) => {
                    _ = shell.update_in(window, |shell, _, cx| {
                        shell.plugin_install_error = Some(error.to_string());
                        cx.notify();
                    });
                    return;
                }
            };

            let Some(path) = selected_path else {
                return;
            };

            let result =
                window.background_executor().spawn(async move { load_plugin_package(path) }).await;
            _ = shell.update_in(window, |shell, _, cx| {
                shell.handle_plugin_package_result(result, cx);
            });
        })
        .detach();
    }

    fn ensure_plugin_install_surface(&mut self, window: &mut Window, cx: &mut Context<Self>) {
        if self.active_tab_matches_url(PLUGIN_SETTINGS_URL) {
            return;
        }

        self.open_internal_tab(PLUGIN_SETTINGS_URL, window, cx);
    }

    pub(super) fn confirm_plugin_install(&mut self, cx: &mut Context<Self>) {
        let Some(pending) = self.pending_plugin_install.take() else {
            cx.notify();
            return;
        };

        let high_risk_confirmed = pending.requires_high_risk_confirmation();
        self.install_plugin_package(pending.package, true, high_risk_confirmed, cx);
    }

    pub(super) fn cancel_plugin_install(&mut self, cx: &mut Context<Self>) {
        self.pending_plugin_install = None;
        cx.notify();
    }

    pub(super) fn request_plugin_uninstall(
        &mut self,
        plugin_id: PluginId,
        plugin_name: String,
        cx: &mut Context<Self>,
    ) {
        self.pending_plugin_uninstall = Some(PendingPluginUninstall::new(plugin_id, plugin_name));
        self.plugin_install_error = None;
        cx.notify();
    }

    pub(super) fn cancel_plugin_uninstall(&mut self, cx: &mut Context<Self>) {
        self.pending_plugin_uninstall = None;
        cx.notify();
    }

    pub(super) fn confirm_plugin_uninstall(&mut self, cx: &mut Context<Self>) {
        let Some(pending) = self.pending_plugin_uninstall.take() else {
            cx.notify();
            return;
        };

        self.uninstall_plugin(pending.plugin_id, cx);
    }

    pub(super) fn set_plugin_enabled(
        &mut self,
        plugin_id: PluginId,
        enabled: bool,
        cx: &mut Context<Self>,
    ) {
        let result = match &mut self.state {
            ShellState::Ready(core) => {
                let action = if enabled {
                    core.enable_plugin(&plugin_id)
                } else {
                    core.disable_plugin(&plugin_id)
                };
                action.map_err(|error| error.to_string())
            }
            ShellState::StartupError(message) => Err(message.clone()),
        };

        self.plugin_install_error = result.err();
        cx.notify();
    }

    pub(super) fn set_plugin_private_window_allowed(
        &mut self,
        plugin_id: PluginId,
        allowed: bool,
        cx: &mut Context<Self>,
    ) {
        let result = match &mut self.state {
            ShellState::Ready(core) => core
                .set_plugin_private_window_allowed(&plugin_id, allowed)
                .map_err(|error| error.to_string()),
            ShellState::StartupError(message) => Err(message.clone()),
        };

        self.plugin_install_error = result.err();
        cx.notify();
    }

    fn uninstall_plugin(&mut self, plugin_id: PluginId, cx: &mut Context<Self>) {
        let result = match &mut self.state {
            ShellState::Ready(core) => PluginPackageStore::application()
                .and_then(|store| store.remove_plugin(&plugin_id))
                .map_err(|error| error.to_string())
                .and_then(|_| core.uninstall_plugin(&plugin_id).map_err(|error| error.to_string())),
            ShellState::StartupError(message) => Err(message.clone()),
        };

        self.plugin_install_error = result.err();
        cx.notify();
    }

    fn handle_plugin_package_result(
        &mut self,
        result: Result<VerifiedPluginPackage, PluginPackageError>,
        cx: &mut Context<Self>,
    ) {
        match result {
            Ok(package) => self.install_plugin_package(package, false, false, cx),
            Err(error) => {
                self.plugin_install_error = Some(error.to_string());
                self.pending_plugin_install = None;
                cx.notify();
            }
        }
    }

    fn install_plugin_package(
        &mut self,
        package: VerifiedPluginPackage,
        local_package_confirmed: bool,
        high_risk_confirmed: bool,
        cx: &mut Context<Self>,
    ) {
        let high_risk_permissions =
            package.manifest().high_risk_permissions().cloned().collect::<Vec<_>>();
        if !local_package_confirmed || (!high_risk_confirmed && !high_risk_permissions.is_empty()) {
            self.pending_plugin_install =
                Some(PendingPluginInstall::new(package, high_risk_permissions));
            self.plugin_install_error = None;
            cx.notify();
            return;
        }

        let result = match &mut self.state {
            ShellState::Ready(core) => PluginPackageStore::application()
                .and_then(|store| store.store(&package))
                .map_err(|error| error.to_string())
                .and_then(|stored_package| {
                    core.install_plugin(stored_package.manifest().clone(), high_risk_confirmed)
                        .map(|_| ())
                        .map_err(|error| error.to_string())
                }),
            ShellState::StartupError(message) => Err(message.clone()),
        };

        self.plugin_install_error = result.err();
        if self.plugin_install_error.is_none() {
            self.pending_plugin_install = None;
        }
        cx.notify();
    }
}

fn load_plugin_package(path: PathBuf) -> Result<VerifiedPluginPackage, PluginPackageError> {
    PluginPackageReader::read_directory_package(&path)
}
