use std::sync::Arc;

use ::settings::AppSettings;
use anyhow::Error;
use chat::{ChatEvent, ChatRegistry};
use common::{CoopImageCache, download_dir};
use device::{DeviceEvent, DeviceRegistry};
use gpui::prelude::FluentBuilder;
use gpui::{
    Action, App, AppContext, Axis, Context, Entity, InteractiveElement, IntoElement, ParentElement,
    Render, SharedString, Styled, Subscription, Task, Window, div, image_cache, px,
};
use nostr_sdk::prelude::*;
use person::{PersonRegistry, shorten_pubkey};
use serde::Deserialize;
use smallvec::{SmallVec, smallvec};
use state::{IMAGE_CACHE_SIZE, NostrRegistry, StateEvent};
use theme::{ActiveTheme, SIDEBAR_WIDTH, Theme, ThemeRegistry};
use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants};
use ui::dock::{ClosePanel, DockArea, DockItem, DockPlacement, PanelView};
use ui::menu::{DropdownMenu, PopupMenuItem};
use ui::notification::{Notification, NotificationKind};
use ui::{Icon, IconName, Root, Sizable, TitleBar, WindowExtension, h_flex, v_flex};

use crate::dialogs::import::ImportIdentity;
use crate::dialogs::restore::RestoreEncryption;
use crate::dialogs::settings;
use crate::panels::{backup, contact_list, greeter, messaging_relays, profile, relay_list};
use crate::sidebar::Sidebar;

mod dialogs;
mod panels;
mod sidebar;

pub fn init(window: &mut Window, cx: &mut App) -> Entity<Workspace> {
    cx.new(|cx| Workspace::new(window, cx))
}

struct DeviceNotifcation;
struct MsgRelayNotification;

#[derive(Action, Clone, PartialEq, Eq, Deserialize)]
#[action(namespace = workspace, no_json)]
enum Command {
    ToggleTheme,

    RefreshMessagingRelays,
    BackupEncryption,
    ImportEncryption,
    RefreshEncryption,
    ResetEncryption,

    ShowRelayList,
    ShowMessaging,
    ShowProfile,
    ShowSettings,
    ShowBackup,
    ShowContactList,
}

pub struct Workspace {
    sidebar: Entity<Sidebar>,
    /// App's Dock Area
    dock: Entity<DockArea>,

    /// App's Image Cache
    image_cache: Entity<CoopImageCache>,

    /// Async tasks
    tasks: Vec<Task<Result<(), Error>>>,

    /// Event subscriptions
    _subscriptions: SmallVec<[Subscription; 6]>,
}

impl Workspace {
    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
        let chat = ChatRegistry::global(cx);
        let device = DeviceRegistry::global(cx);
        let nostr = NostrRegistry::global(cx);

        let sidebar = cx.new(|cx| Sidebar::new(window, cx));
        let dock = cx.new(|cx| DockArea::new(window, cx));
        let image_cache = CoopImageCache::new(IMAGE_CACHE_SIZE, cx);

        let mut subscriptions = smallvec![];

        subscriptions.push(
            // Observe system appearance and update theme
            cx.observe_window_appearance(window, |_this, window, cx| {
                Theme::sync_system_appearance(Some(window), cx);
            }),
        );

        subscriptions.push(
            // Subscribe to the nostr events
            cx.subscribe_in(&nostr, window, move |this, _state, event, window, cx| {
                match event {
                    StateEvent::SignerChanged => {
                        window.close_all_modals(cx);
                    }
                    StateEvent::NoSigner => {
                        this.import_identity(window, cx);
                    }
                    _ => {}
                };
            }),
        );

        subscriptions.push(
            // Observe all events emitted by the device registry
            cx.subscribe_in(&device, window, |_this, _device, event, window, cx| {
                match event {
                    DeviceEvent::Requesting => {
                        const MSG: &str =
                            "Please open other client and approve the request for encryption key.";

                        let note = Notification::new()
                            .id::<DeviceNotifcation>()
                            .autohide(false)
                            .title("Wait for approval")
                            .message(MSG)
                            .with_kind(NotificationKind::Info);

                        window.push_notification(note, cx);
                    }
                    DeviceEvent::NotSet => {
                        const MSG: &str =
                            "User're not setup encryption key yet. Do you want to create one?";

                        let note = Notification::new()
                            .id::<DeviceNotifcation>()
                            .message(MSG)
                            .with_kind(NotificationKind::Info)
                            .action(|_this, _window, _cx| {
                                Button::new("retry").label("Retry").on_click(
                                    move |_this, window, cx| {
                                        let device = DeviceRegistry::global(cx);
                                        device.update(cx, |this, cx| {
                                            this.set_announcement(Keys::generate(), cx);
                                        });
                                        window.clear_notification::<DeviceNotifcation>(cx);
                                    },
                                )
                            });

                        window.push_notification(note, cx);
                    }
                    DeviceEvent::Set => {
                        let note = Notification::new()
                            .id::<DeviceNotifcation>()
                            .message("Encryption Key has been set")
                            .with_kind(NotificationKind::Success);

                        window.push_notification(note, cx);
                    }
                    DeviceEvent::Error(error) => {
                        window.push_notification(Notification::error(error).autohide(false), cx);
                    }
                };
            }),
        );

        subscriptions.push(
            // Observe all events emitted by the chat registry
            cx.subscribe_in(&chat, window, move |this, chat, ev, window, cx| {
                match ev {
                    ChatEvent::InboxRelayNotFound => {
                        const MSG: &str = "Messaging Relays not found. Cannot receive messages.";

                        window.push_notification(
                            Notification::warning(MSG)
                                .id::<MsgRelayNotification>()
                                .autohide(false)
                                .action(|_this, _window, _cx| {
                                    Button::new("retry").label("Retry").on_click(
                                        move |_this, window, cx| {
                                            let chat = ChatRegistry::global(cx);
                                            chat.update(cx, |this, cx| {
                                                this.get_metadata(cx);
                                            });
                                            window.clear_notification::<MsgRelayNotification>(cx);
                                        },
                                    )
                                }),
                            cx,
                        );
                    }
                    ChatEvent::OpenRoom(id) => {
                        if let Some(room) = chat.read(cx).room(id, cx) {
                            this.dock.update(cx, |this, cx| {
                                this.add_panel(
                                    Arc::new(chat_ui::init(room, window, cx)),
                                    DockPlacement::Center,
                                    window,
                                    cx,
                                );
                            });
                        }
                    }
                    ChatEvent::CloseRoom(..) => {
                        this.dock.update(cx, |this, cx| {
                            // Force focus to the tab panel
                            this.focus_tab_panel(window, cx);

                            // Dispatch the close panel action
                            cx.defer_in(window, |_, window, cx| {
                                window.dispatch_action(Box::new(ClosePanel), cx);
                                window.close_all_modals(cx);
                            });
                        });
                    }
                    ChatEvent::Error(error) => {
                        window.push_notification(Notification::error(error).autohide(false), cx);
                    }
                    _ => {}
                };
            }),
        );

        cx.defer_in(window, |this, window, cx| {
            let dock = this.dock.downgrade();
            let greeter = Arc::new(greeter::init(window, cx));
            let tabs = DockItem::tabs(vec![greeter], None, &dock, window, cx);
            let center = DockItem::split(Axis::Vertical, vec![tabs], &dock, window, cx);

            this.dock.update(cx, |this, cx| {
                this.set_center(center, window, cx);
            });
        });

        Self {
            sidebar,
            dock,
            image_cache,
            tasks: vec![],
            _subscriptions: subscriptions,
        }
    }

    /// Add panel to the dock
    pub fn add_panel<P>(panel: P, placement: DockPlacement, window: &mut Window, cx: &mut App)
    where
        P: PanelView,
    {
        if let Some(root) = window.root::<Root>().flatten()
            && let Ok(workspace) = root.read(cx).view().clone().downcast::<Self>()
        {
            workspace.update(cx, |this, cx| {
                this.dock.update(cx, |this, cx| {
                    this.add_panel(Arc::new(panel), placement, window, cx);
                });
            });
        }
    }

    /// Handle command events
    fn on_command(&mut self, command: &Command, window: &mut Window, cx: &mut Context<Self>) {
        match command {
            Command::ShowSettings => {
                let view = settings::init(window, cx);

                window.open_modal(cx, move |this, _window, _cx| {
                    this.width(px(520.))
                        .show_close(true)
                        .pb_2()
                        .title("Preferences")
                        .child(view.clone())
                });
            }
            Command::ShowProfile => {
                let nostr = NostrRegistry::global(cx);

                if let Some(public_key) = nostr.read(cx).current_user() {
                    self.dock.update(cx, |this, cx| {
                        this.add_panel(
                            Arc::new(profile::init(public_key, window, cx)),
                            DockPlacement::Left,
                            window,
                            cx,
                        );
                    });
                }
            }
            Command::ShowContactList => {
                self.dock.update(cx, |this, cx| {
                    this.add_panel(
                        Arc::new(contact_list::init(window, cx)),
                        DockPlacement::Left,
                        window,
                        cx,
                    );
                });
            }
            Command::ShowBackup => {
                self.dock.update(cx, |this, cx| {
                    this.add_panel(
                        Arc::new(backup::init(window, cx)),
                        DockPlacement::Left,
                        window,
                        cx,
                    );
                });
            }
            Command::ShowMessaging => {
                self.dock.update(cx, |this, cx| {
                    this.add_panel(
                        Arc::new(messaging_relays::init(window, cx)),
                        DockPlacement::Left,
                        window,
                        cx,
                    );
                });
            }
            Command::RefreshMessagingRelays => {
                let chat = ChatRegistry::global(cx);
                // Trigger a refresh of the chat registry
                chat.update(cx, |this, cx| {
                    this.reload(cx);
                });
            }
            Command::ShowRelayList => {
                self.dock.update(cx, |this, cx| {
                    this.add_panel(
                        Arc::new(relay_list::init(window, cx)),
                        DockPlacement::Right,
                        window,
                        cx,
                    );
                });
            }
            Command::RefreshEncryption => {
                let device = DeviceRegistry::global(cx);
                device.update(cx, |this, cx| {
                    this.get_announcement(cx);
                });
            }
            Command::ResetEncryption => {
                self.confirm_reset_encryption(window, cx);
            }
            Command::ToggleTheme => {
                self.theme_selector(window, cx);
            }
            Command::BackupEncryption => {
                let device = DeviceRegistry::global(cx).downgrade();
                let save_dialog = cx.prompt_for_new_path(download_dir(), Some("encryption.txt"));

                self.tasks.push(cx.spawn_in(window, async move |_this, cx| {
                    // Get the output path from the save dialog
                    let output_path = match save_dialog.await {
                        Ok(Ok(Some(path))) => path,
                        Ok(Ok(None)) | Err(_) => return Ok(()),
                        Ok(Err(error)) => {
                            cx.update(|window, cx| {
                                let message = format!("Failed to pick save location: {error:#}");
                                let note = Notification::error(message).autohide(false);
                                window.push_notification(note, cx);
                            })?;
                            return Ok(());
                        }
                    };

                    // Get the backup task
                    let backup =
                        device.read_with(cx, |this, cx| this.backup(output_path.clone(), cx))?;

                    // Run the backup task
                    backup.await?;

                    // Open the backup file with the system's default application
                    cx.update(|_window, cx| {
                        cx.open_with_system(output_path.as_path());
                    })?;

                    Ok(())
                }));
            }
            Command::ImportEncryption => {
                self.import_encryption(window, cx);
            }
        }
    }

    fn confirm_reset_encryption(&mut self, window: &mut Window, cx: &mut Context<Self>) {
        const ENC_MSG: &str = "Encryption Key is a special key that used to encrypt and decrypt your messages. \
                               Your identity is completely decoupled from all encryption processes to protect your privacy.";

        const ENC_WARN: &str = "By resetting your encryption key, you will lose access to \
                                all your encrypted messages before. This action cannot be undone.";

        let device = DeviceRegistry::global(cx);
        let ent = device.downgrade();

        window.open_modal(cx, move |this, _window, cx| {
            let ent = ent.clone();

            this.confirm()
                .show_close(true)
                .title("Reset Encryption Key")
                .child(
                    v_flex()
                        .gap_1()
                        .text_sm()
                        .child(SharedString::from(ENC_MSG))
                        .child(
                            div()
                                .italic()
                                .text_color(cx.theme().text_danger)
                                .child(SharedString::from(ENC_WARN)),
                        ),
                )
                .on_ok(move |_ev, _window, cx| {
                    ent.update(cx, |this, cx| {
                        this.set_announcement(Keys::generate(), cx);
                    })
                    .ok();
                    // true to close modal
                    true
                })
        });
    }

    fn import_encryption(&mut self, window: &mut Window, cx: &mut Context<Self>) {
        let restore = cx.new(|cx| RestoreEncryption::new(window, cx));
        window.open_modal(cx, move |this, _window, _cx| {
            this.width(px(420.))
                .title("Restore Encryption")
                .child(restore.clone())
        });
    }

    fn import_identity(&mut self, window: &mut Window, cx: &mut Context<Self>) {
        let import = cx.new(|cx| ImportIdentity::new(window, cx));

        window.open_modal(cx, move |this, _window, _cx| {
            this.width(px(450.))
                .show_close(false)
                .overlay_closable(false)
                .keyboard(false)
                .title("Onboarding")
                .child(import.clone())
        });
    }

    fn theme_selector(&mut self, window: &mut Window, cx: &mut Context<Self>) {
        window.open_modal(cx, move |this, _window, cx| {
            let registry = ThemeRegistry::global(cx);
            let themes = registry.read(cx).themes();

            this.width(px(520.))
                .show_close(true)
                .title("Select theme")
                .child(v_flex().gap_2().w_full().children({
                    let mut items = vec![];

                    for (ix, (path, theme)) in themes.iter().enumerate() {
                        items.push(
                            h_flex()
                                .id(ix)
                                .group("")
                                .px_2()
                                .h_8()
                                .w_full()
                                .justify_between()
                                .rounded(cx.theme().radius)
                                .bg(cx.theme().ghost_element_background)
                                .hover(|this| this.bg(cx.theme().ghost_element_hover))
                                .child(
                                    h_flex()
                                        .gap_1p5()
                                        .flex_1()
                                        .text_sm()
                                        .child(theme.name.clone())
                                        .child(
                                            div()
                                                .text_xs()
                                                .italic()
                                                .text_color(cx.theme().text_muted)
                                                .child(theme.author.clone()),
                                        ),
                                )
                                .child(
                                    h_flex()
                                        .gap_1()
                                        .invisible()
                                        .group_hover("", |this| this.visible())
                                        .child(
                                            Button::new(format!("url-{ix}"))
                                                .icon(IconName::Link)
                                                .ghost()
                                                .small()
                                                .on_click({
                                                    let theme = theme.clone();
                                                    move |_ev, _window, cx| {
                                                        cx.open_url(&theme.url);
                                                    }
                                                }),
                                        )
                                        .child(
                                            Button::new(format!("set-{ix}"))
                                                .icon(IconName::Check)
                                                .primary()
                                                .small()
                                                .on_click({
                                                    let path = path.clone();
                                                    move |_ev, window, cx| {
                                                        let settings = AppSettings::global(cx);
                                                        let path = path.clone();

                                                        settings.update(cx, |this, cx| {
                                                            this.set_theme(path, window, cx);
                                                        })
                                                    }
                                                }),
                                        ),
                                ),
                        );
                    }

                    items
                }))
        });
    }

    fn titlebar_left(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
        let nostr = NostrRegistry::global(cx);
        let current_user = nostr.read(cx).current_user();

        h_flex()
            .flex_shrink_0()
            .gap_2()
            .when_none(&current_user, |this| {
                this.child(
                    div()
                        .text_xs()
                        .text_color(cx.theme().text_muted)
                        .child(SharedString::from("Import your identity to continue")),
                )
            })
            .when_some(current_user.as_ref(), |this, public_key| {
                let persons = PersonRegistry::global(cx);
                let profile = persons.read(cx).get(public_key, cx);
                let avatar = profile.avatar();
                let name = profile.name();

                this.child(
                    Button::new("current-user")
                        .child(Avatar::new(avatar.clone()).xsmall())
                        .small()
                        .caret()
                        .compact()
                        .transparent()
                        .dropdown_menu(move |this, _window, _cx| {
                            let avatar = avatar.clone();
                            let name = name.clone();

                            this.min_w(px(256.))
                                .item(PopupMenuItem::element(move |_window, cx| {
                                    h_flex()
                                        .gap_1p5()
                                        .text_xs()
                                        .text_color(cx.theme().text_muted)
                                        .child(Avatar::new(avatar.clone()).xsmall())
                                        .child(name.clone())
                                }))
                                .separator()
                                .menu_with_icon(
                                    "Profile",
                                    IconName::Profile,
                                    Box::new(Command::ShowProfile),
                                )
                                .menu_with_icon(
                                    "Contact List",
                                    IconName::Book,
                                    Box::new(Command::ShowContactList),
                                )
                                .menu_with_icon(
                                    "Backup",
                                    IconName::UserKey,
                                    Box::new(Command::ShowBackup),
                                )
                                .menu_with_icon(
                                    "Themes",
                                    IconName::Sun,
                                    Box::new(Command::ToggleTheme),
                                )
                                .separator()
                                .menu_with_icon(
                                    "Settings",
                                    IconName::Settings,
                                    Box::new(Command::ShowSettings),
                                )
                        }),
                )
            })
    }

    fn titlebar_right(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
        let chat = ChatRegistry::global(cx);
        let nip4e_enabled = AppSettings::get_nip4e(cx);
        let nostr = NostrRegistry::global(cx);

        let Some(public_key) = nostr.read(cx).current_user() else {
            return div();
        };

        let persons = PersonRegistry::global(cx);
        let profile = persons.read(cx).get(&public_key, cx);
        let announcement = profile.announcement();

        h_flex()
            .when(!cx.theme().platform.is_mac(), |this| this.pr_2())
            .gap_2()
            .when(nip4e_enabled, |this| {
                this.child(
                    Button::new("key")
                        .icon(IconName::UserKey)
                        .tooltip("Decoupled encryption key")
                        .small()
                        .ghost()
                        .dropdown_menu(move |this, _window, _cx| {
                            this.min_w(px(260.))
                                .label("Encryption Key")
                                .when_some(announcement.as_ref(), |this, announcement| {
                                    let name = announcement.client_name();
                                    let pkey = shorten_pubkey(announcement.public_key(), 8);

                                    this.item(PopupMenuItem::element(move |_window, cx| {
                                        h_flex()
                                            .gap_1()
                                            .text_sm()
                                            .child(
                                                Icon::new(IconName::Device)
                                                    .small()
                                                    .text_color(cx.theme().icon_muted),
                                            )
                                            .child(name.clone())
                                    }))
                                    .item(
                                        PopupMenuItem::element(move |_window, cx| {
                                            h_flex()
                                                .gap_1()
                                                .text_sm()
                                                .child(
                                                    Icon::new(IconName::UserKey)
                                                        .small()
                                                        .text_color(cx.theme().icon_muted),
                                                )
                                                .child(SharedString::from(pkey.clone()))
                                        }),
                                    )
                                })
                                .separator()
                                .menu_with_icon(
                                    "Backup",
                                    IconName::Shield,
                                    Box::new(Command::BackupEncryption),
                                )
                                .menu_with_icon(
                                    "Restore from secret key",
                                    IconName::Usb,
                                    Box::new(Command::ImportEncryption),
                                )
                                .separator()
                                .menu_with_icon(
                                    "Reload",
                                    IconName::Refresh,
                                    Box::new(Command::RefreshEncryption),
                                )
                                .menu_with_icon(
                                    "Reset",
                                    IconName::Warning,
                                    Box::new(Command::ResetEncryption),
                                )
                        }),
                )
            })
            .child(
                Button::new("inbox")
                    .icon(IconName::Inbox)
                    .small()
                    .ghost()
                    .dropdown_menu(move |this, _window, cx| {
                        let urls: Vec<(SharedString, SharedString)> = profile
                            .messaging_relays()
                            .iter()
                            .map(|url| {
                                (
                                    SharedString::from(url.to_string()),
                                    chat.read(cx).count_messages(url).to_string().into(),
                                )
                            })
                            .collect();

                        // Header
                        let menu = this.min_w(px(260.)).label("Messaging Relays");

                        // Content
                        let menu = urls.into_iter().fold(menu, |this, (url, count)| {
                            this.item(PopupMenuItem::element(move |_window, cx| {
                                h_flex()
                                    .px_1()
                                    .w_full()
                                    .text_sm()
                                    .justify_between()
                                    .child(url.clone())
                                    .child(
                                        div()
                                            .text_xs()
                                            .text_color(cx.theme().text_muted)
                                            .child(count.clone()),
                                    )
                            }))
                        });

                        // Footer
                        menu.separator()
                            .menu_with_icon(
                                "Manage gossip relays",
                                IconName::Relay,
                                Box::new(Command::ShowRelayList),
                            )
                            .menu_with_icon(
                                "Manage messaging relays",
                                IconName::Relay,
                                Box::new(Command::ShowMessaging),
                            )
                            .separator()
                            .menu_with_icon(
                                "Reload",
                                IconName::Refresh,
                                Box::new(Command::RefreshMessagingRelays),
                            )
                    }),
            )
    }
}

impl Render for Workspace {
    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        let modal_layer = Root::render_modal_layer(window, cx);
        let notification_layer = Root::render_notification_layer(window, cx);

        div()
            .id("workspace")
            .on_action(cx.listener(Self::on_command))
            .relative()
            .size_full()
            .child(
                image_cache(self.image_cache.clone())
                    .relative()
                    .size_full()
                    .child(
                        v_flex()
                            .size_full()
                            // Title Bar
                            .child(
                                TitleBar::new()
                                    .child(self.titlebar_left(cx))
                                    .child(self.titlebar_right(cx)),
                            )
                            // Main
                            .child(
                                h_flex()
                                    .size_full()
                                    .child(
                                        div()
                                            .flex_shrink_0()
                                            .h_full()
                                            .w(SIDEBAR_WIDTH)
                                            .child(self.sidebar.clone()),
                                    )
                                    .child(self.dock.clone()),
                            ),
                    ),
            )
            // Notifications
            .children(notification_layer)
            // Modals
            .children(modal_layer)
    }
}
