use chats::{registry::ChatRegistry, room::Room};
use common::{
    profile::NostrProfile,
    utils::{random_name, signer_public_key},
};
use gpui::{
    div, img, impl_internal_actions, prelude::FluentBuilder, px, relative, uniform_list, App,
    AppContext, Context, Entity, FocusHandle, InteractiveElement, IntoElement, ParentElement,
    Render, SharedString, StatefulInteractiveElement, Styled, Subscription, TextAlign, Window,
};
use nostr_sdk::prelude::*;
use serde::Deserialize;
use smol::Timer;
use state::get_client;
use std::{collections::HashSet, time::Duration};
use tokio::sync::oneshot;
use ui::{
    button::{Button, ButtonRounded},
    input::{InputEvent, TextInput},
    theme::{scale::ColorScaleStep, ActiveTheme},
    ContextModal, Icon, IconName, Sizable, Size, StyledExt,
};

const ALERT: &str =
    "Start a conversation with someone using their npub or NIP-05 (like foo@bar.com).";

#[derive(Clone, PartialEq, Eq, Deserialize)]
struct SelectContact(PublicKey);

impl_internal_actions!(contacts, [SelectContact]);

pub struct Compose {
    title_input: Entity<TextInput>,
    user_input: Entity<TextInput>,
    contacts: Entity<Vec<NostrProfile>>,
    selected: Entity<HashSet<PublicKey>>,
    focus_handle: FocusHandle,
    is_loading: bool,
    is_submitting: bool,
    error_message: Entity<Option<SharedString>>,
    #[allow(dead_code)]
    subscriptions: Vec<Subscription>,
}

impl Compose {
    pub fn new(window: &mut Window, cx: &mut Context<'_, Self>) -> Self {
        let contacts = cx.new(|_| Vec::new());
        let selected = cx.new(|_| HashSet::new());
        let error_message = cx.new(|_| None);
        let mut subscriptions = Vec::new();

        let title_input = cx.new(|cx| {
            let name = random_name(2);
            let mut input = TextInput::new(window, cx)
                .appearance(false)
                .text_size(Size::XSmall);

            input.set_placeholder("Family... . (Optional)");
            input.set_text(name, window, cx);
            input
        });

        let user_input = cx.new(|cx| {
            TextInput::new(window, cx)
                .text_size(ui::Size::Small)
                .small()
                .placeholder("npub1...")
        });

        // Handle Enter event for user input
        subscriptions.push(cx.subscribe_in(
            &user_input,
            window,
            move |this, _, input_event, window, cx| {
                if let InputEvent::PressEnter = input_event {
                    this.add(window, cx);
                }
            },
        ));

        let client = get_client();
        let (tx, rx) = oneshot::channel::<Vec<NostrProfile>>();

        cx.background_spawn(async move {
            if let Ok(public_key) = signer_public_key(client).await {
                if let Ok(profiles) = client.database().contacts(public_key).await {
                    let members: Vec<NostrProfile> = profiles
                        .into_iter()
                        .map(|profile| NostrProfile::new(profile.public_key(), profile.metadata()))
                        .collect();

                    _ = tx.send(members);
                }
            }
        })
        .detach();

        cx.spawn(|this, cx| async move {
            if let Ok(contacts) = rx.await {
                _ = cx.update(|cx| {
                    this.update(cx, |this, cx| {
                        this.contacts.update(cx, |this, cx| {
                            this.extend(contacts);
                            cx.notify();
                        });
                    })
                });
            }
        })
        .detach();

        Self {
            title_input,
            user_input,
            contacts,
            selected,
            error_message,
            is_loading: false,
            is_submitting: false,
            focus_handle: cx.focus_handle(),
            subscriptions,
        }
    }

    pub fn compose(&mut self, window: &mut Window, cx: &mut Context<Self>) {
        if self.selected.read(cx).is_empty() {
            self.set_error(Some("You need to add at least 1 receiver".into()), cx);
            return;
        }

        // Show loading spinner
        self.set_submitting(true, cx);

        // Get all pubkeys
        let pubkeys: Vec<PublicKey> = self.selected.read(cx).iter().copied().collect();

        // Convert selected pubkeys into Nostr tags
        let mut tag_list: Vec<Tag> = pubkeys.iter().map(|pk| Tag::public_key(*pk)).collect();

        // Add subject if it is present
        if !self.title_input.read(cx).text().is_empty() {
            tag_list.push(Tag::custom(
                TagKind::Subject,
                vec![self.title_input.read(cx).text().to_string()],
            ));
        }

        let tags = Tags::new(tag_list);
        let client = get_client();
        let window_handle = window.window_handle();
        let (tx, rx) = oneshot::channel::<Event>();

        cx.background_spawn(async move {
            let signer = client.signer().await.expect("Signer is required");
            // [IMPORTANT]
            // Make sure this event is never send,
            // this event existed just use for convert to Coop's Chat Room later.
            if let Ok(event) = EventBuilder::private_msg_rumor(*pubkeys.last().unwrap(), "")
                .tags(tags)
                .sign(&signer)
                .await
            {
                _ = tx.send(event)
            };
        })
        .detach();

        cx.spawn(|this, mut cx| async move {
            if let Ok(event) = rx.await {
                _ = cx.update_window(window_handle, |_, window, cx| {
                    // Stop loading spinner
                    _ = this.update(cx, |this, cx| {
                        this.set_submitting(false, cx);
                    });

                    if let Some(chats) = ChatRegistry::global(cx) {
                        let room = Room::parse(&event, cx);

                        chats.update(cx, |state, cx| match state.new_room(room, cx) {
                            Ok(_) => {
                                // TODO: open chat panel
                                window.close_modal(cx);
                            }
                            Err(e) => {
                                _ = this.update(cx, |this, cx| {
                                    this.set_error(Some(e.to_string().into()), cx);
                                });
                            }
                        });
                    }
                });
            }
        })
        .detach();
    }

    pub fn label(&self, _window: &Window, cx: &App) -> SharedString {
        if self.selected.read(cx).len() > 1 {
            "Create Group DM".into()
        } else {
            "Create DM".into()
        }
    }

    pub fn is_submitting(&self) -> bool {
        self.is_submitting
    }

    fn add(&mut self, window: &mut Window, cx: &mut Context<Self>) {
        let window_handle = window.window_handle();
        let content = self.user_input.read(cx).text().to_string();

        // Show loading spinner
        self.set_loading(true, cx);

        if let Ok(public_key) = PublicKey::parse(&content) {
            if self
                .contacts
                .read(cx)
                .iter()
                .any(|c| c.public_key() == public_key)
            {
                self.set_loading(false, cx);
                return;
            };

            cx.spawn(|this, mut cx| async move {
                let (tx, rx) = oneshot::channel::<Metadata>();

                cx.background_spawn(async move {
                    let client = get_client();
                    let metadata = (client
                        .fetch_metadata(public_key, Duration::from_secs(3))
                        .await)
                        .unwrap_or_default();

                    _ = tx.send(metadata);
                })
                .detach();

                if let Ok(metadata) = rx.await {
                    _ = cx.update_window(window_handle, |_, window, cx| {
                        _ = this.update(cx, |this, cx| {
                            this.contacts.update(cx, |this, cx| {
                                this.insert(0, NostrProfile::new(public_key, metadata));
                                cx.notify();
                            });

                            this.selected.update(cx, |this, cx| {
                                this.insert(public_key);
                                cx.notify();
                            });

                            // Stop loading indicator
                            this.set_loading(false, cx);

                            // Clear input
                            this.user_input.update(cx, |this, cx| {
                                this.set_text("", window, cx);
                                cx.notify();
                            });
                        });
                    });
                }
            })
            .detach();
        } else {
            self.set_loading(false, cx);
            self.set_error(Some("Public Key is not valid".into()), cx);
        }
    }

    fn set_error(&mut self, error: Option<SharedString>, cx: &mut Context<Self>) {
        self.error_message.update(cx, |this, cx| {
            *this = error;
            cx.notify();
        });

        // Dismiss error after 2 seconds
        cx.spawn(|this, cx| async move {
            Timer::after(Duration::from_secs(2)).await;

            _ = cx.update(|cx| {
                this.update(cx, |this, cx| {
                    this.set_error(None, cx);
                })
            });
        })
        .detach();
    }

    fn set_loading(&mut self, status: bool, cx: &mut Context<Self>) {
        self.is_loading = status;
        cx.notify();
    }

    fn set_submitting(&mut self, status: bool, cx: &mut Context<Self>) {
        self.is_submitting = status;
        cx.notify();
    }

    fn on_action_select(
        &mut self,
        action: &SelectContact,
        _window: &mut Window,
        cx: &mut Context<Self>,
    ) {
        self.selected.update(cx, |this, cx| {
            if this.contains(&action.0) {
                this.remove(&action.0);
            } else {
                this.insert(action.0);
            };
            cx.notify();
        });
    }
}

impl Render for Compose {
    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        div()
            .track_focus(&self.focus_handle)
            .on_action(cx.listener(Self::on_action_select))
            .flex()
            .flex_col()
            .gap_1()
            .child(
                div()
                    .px_2()
                    .text_xs()
                    .text_color(cx.theme().base.step(cx, ColorScaleStep::ELEVEN))
                    .child(ALERT),
            )
            .when_some(self.error_message.read(cx).as_ref(), |this, msg| {
                this.child(
                    div()
                        .px_2()
                        .text_xs()
                        .text_color(cx.theme().danger)
                        .child(msg.clone()),
                )
            })
            .child(
                div().flex().flex_col().child(
                    div()
                        .h_10()
                        .px_2()
                        .border_b_1()
                        .border_color(cx.theme().base.step(cx, ColorScaleStep::FIVE))
                        .flex()
                        .items_center()
                        .gap_1()
                        .child(div().text_xs().font_semibold().child("Title:"))
                        .child(self.title_input.clone()),
                ),
            )
            .child(
                div()
                    .flex()
                    .flex_col()
                    .gap_2()
                    .child(div().px_2().text_xs().font_semibold().child("To:"))
                    .child(
                        div()
                            .flex()
                            .items_center()
                            .gap_2()
                            .px_2()
                            .child(
                                Button::new("add_user_to_compose_btn")
                                    .icon(IconName::Plus)
                                    .small()
                                    .rounded(ButtonRounded::Size(px(9999.)))
                                    .loading(self.is_loading)
                                    .on_click(
                                        cx.listener(|this, _, window, cx| this.add(window, cx)),
                                    ),
                            )
                            .child(self.user_input.clone()),
                    )
                    .map(|this| {
                        let contacts = self.contacts.read(cx).clone();
                        let view = cx.entity();

                        if contacts.is_empty() {
                            this.child(
                                div()
                                    .w_full()
                                    .h_24()
                                    .flex()
                                    .flex_col()
                                    .items_center()
                                    .justify_center()
                                    .text_align(TextAlign::Center)
                                    .child(
                                        div()
                                            .text_xs()
                                            .font_semibold()
                                            .line_height(relative(1.2))
                                            .child("No contacts"),
                                    )
                                    .child(
                                        div()
                                            .text_xs()
                                            .text_color(
                                                cx.theme().base.step(cx, ColorScaleStep::ELEVEN),
                                            )
                                            .child("Your recently contacts will appear here."),
                                    ),
                            )
                        } else {
                            this.child(
                                uniform_list(
                                    view,
                                    "contacts",
                                    contacts.len(),
                                    move |this, range, _window, cx| {
                                        let selected = this.selected.read(cx);
                                        let mut items = Vec::new();

                                        for ix in range {
                                            let item = contacts.get(ix).unwrap().clone();
                                            let is_select = selected.contains(&item.public_key());

                                            items.push(
                                                div()
                                                    .id(ix)
                                                    .w_full()
                                                    .h_9()
                                                    .px_2()
                                                    .flex()
                                                    .items_center()
                                                    .justify_between()
                                                    .child(
                                                        div()
                                                            .flex()
                                                            .items_center()
                                                            .gap_2()
                                                            .text_xs()
                                                            .child(
                                                                div().flex_shrink_0().child(
                                                                    img(item.avatar()).size_6(),
                                                                ),
                                                            )
                                                            .child(item.name()),
                                                    )
                                                    .when(is_select, |this| {
                                                        this.child(
                                                            Icon::new(IconName::CircleCheck)
                                                                .size_3()
                                                                .text_color(cx.theme().base.step(
                                                                    cx,
                                                                    ColorScaleStep::TWELVE,
                                                                )),
                                                        )
                                                    })
                                                    .hover(|this| {
                                                        this.bg(cx
                                                            .theme()
                                                            .base
                                                            .step(cx, ColorScaleStep::THREE))
                                                    })
                                                    .on_click(move |_, window, cx| {
                                                        window.dispatch_action(
                                                            Box::new(SelectContact(
                                                                item.public_key(),
                                                            )),
                                                            cx,
                                                        );
                                                    }),
                                            );
                                        }

                                        items
                                    },
                                )
                                .min_h(px(250.)),
                            )
                        }
                    }),
            )
    }
}
