use std::borrow::Cow;
use std::cell::Cell;
use std::collections::HashSet;
use std::hash::Hash;
use std::rc::Rc;
use std::sync::Arc;

use anyhow::{Context as AnyhowContext, Error, anyhow};
use gpui::{
    App, AppContext, Context, Entity, Global, IntoElement, ParentElement, SharedString, Styled,
    Task, Window, div, relative,
};
use gpui_component::button::Button;
use gpui_component::notification::{Notification, NotificationType};
use gpui_component::{ActiveTheme, Disableable, WindowExt, v_flex};
use nostr_sdk::prelude::*;
use settings::{AppSettings, AuthMode};
use smallvec::{SmallVec, smallvec};
use state::NostrRegistry;

const AUTH_MESSAGE: &str =
    "Approve the authentication request to allow Coop to continue sending or receiving events.";

pub fn init(window: &mut Window, cx: &mut App) {
    RelayAuth::set_global(cx.new(|cx| RelayAuth::new(window, cx)), cx);
}

/// Authentication request
#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
struct AuthRequest {
    url: RelayUrl,
    challenge: String,
}

impl AuthRequest {
    pub fn new<S>(challenge: S, url: RelayUrl) -> Self
    where
        S: Into<String>,
    {
        Self {
            challenge: challenge.into(),
            url,
        }
    }

    pub fn url(&self) -> &RelayUrl {
        &self.url
    }

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

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
enum Signal {
    Auth(Arc<AuthRequest>),
    Pending((EventId, RelayUrl)),
}

struct GlobalRelayAuth(Entity<RelayAuth>);

impl Global for GlobalRelayAuth {}

// Relay authentication
#[derive(Debug)]
pub struct RelayAuth {
    /// Pending events waiting for resend after authentication
    pending_events: HashSet<(EventId, RelayUrl)>,

    /// Tasks for asynchronous operations
    _tasks: SmallVec<[Task<()>; 2]>,
}

impl RelayAuth {
    /// Retrieve the global relay auth state
    pub fn global(cx: &App) -> Entity<Self> {
        cx.global::<GlobalRelayAuth>().0.clone()
    }

    /// Set the global relay auth instance
    fn set_global(state: Entity<Self>, cx: &mut App) {
        cx.set_global(GlobalRelayAuth(state));
    }

    /// Create a new relay auth instance
    fn new(window: &mut Window, cx: &mut Context<Self>) -> Self {
        let nostr = NostrRegistry::global(cx);
        let client = nostr.read(cx).client();

        let mut tasks = smallvec![];

        // Channel for communication between nostr and gpui
        let (tx, rx) = flume::bounded::<Signal>(256);

        tasks.push(cx.background_spawn(async move {
            let mut notifications = client.notifications();
            let mut challenges: HashSet<Cow<'_, str>> = HashSet::default();

            while let Some(notification) = notifications.next().await {
                if let ClientNotification::Message { relay_url, message } = notification {
                    match *message {
                        RelayMessage::Auth { challenge } => {
                            if challenges.insert(challenge.clone()) {
                                let request = Arc::new(AuthRequest::new(challenge, relay_url));
                                let signal = Signal::Auth(request);

                                tx.send_async(signal).await.ok();
                            }
                        }
                        RelayMessage::Ok {
                            event_id, message, ..
                        } => {
                            let msg = MachineReadablePrefix::parse(&message);

                            // Handle authentication messages
                            if let Some(MachineReadablePrefix::AuthRequired) = msg {
                                let signal = Signal::Pending((event_id, relay_url));
                                tx.send_async(signal).await.ok();
                            }
                        }
                        _ => {}
                    }
                }
            }
        }));

        tasks.push(cx.spawn_in(window, async move |this, cx| {
            while let Ok(signal) = rx.recv_async().await {
                match signal {
                    Signal::Auth(req) => {
                        this.update_in(cx, |this, window, cx| {
                            this.handle_auth(&req, window, cx);
                        })
                        .ok();
                    }
                    Signal::Pending((event_id, relay_url)) => {
                        this.update_in(cx, |this, _window, cx| {
                            this.insert_pending_event(event_id, relay_url, cx);
                        })
                        .ok();
                    }
                }
            }
        }));

        Self {
            pending_events: HashSet::default(),
            _tasks: tasks,
        }
    }

    /// Insert a pending event waiting for resend after authentication
    fn insert_pending_event(&mut self, id: EventId, relay: RelayUrl, cx: &mut Context<Self>) {
        self.pending_events.insert((id, relay));
        cx.notify();
    }

    /// Get all pending events for a specific relay,
    fn get_pending_events(&self, relay: &RelayUrl, _cx: &App) -> Vec<EventId> {
        self.pending_events
            .iter()
            .filter(|(_, pending_relay)| pending_relay == relay)
            .map(|(id, _relay)| id)
            .cloned()
            .collect()
    }

    /// Clear all pending events for a specific relay,
    fn clear_pending_events(&mut self, relay: &RelayUrl, cx: &mut Context<Self>) {
        self.pending_events
            .retain(|(_, pending_relay)| pending_relay != relay);
        cx.notify();
    }

    /// Handle authentication request
    fn handle_auth(&mut self, req: &Arc<AuthRequest>, window: &mut Window, cx: &mut Context<Self>) {
        let settings = AppSettings::global(cx);
        let trusted_relay = settings.read(cx).trusted_relay(req.url(), cx);
        let mode = AppSettings::get_auth_mode(cx);

        if trusted_relay && mode == AuthMode::Auto {
            // Automatically authenticate if the relay is authenticated before
            self.response(req, window, cx);
        } else {
            // Otherwise open the auth request popup
            self.ask_for_approval(req, window, cx);
        }
    }

    /// Send auth response and wait for confirmation
    fn auth(&self, req: &Arc<AuthRequest>, cx: &App) -> Task<Result<(), Error>> {
        let nostr = NostrRegistry::global(cx);
        let client = nostr.read(cx).client();
        let req = req.clone();

        // Get all pending events for the relay
        let pending_events = self.get_pending_events(req.url(), cx);

        cx.background_spawn(async move {
            // Construct event
            let builder = EventBuilder::auth(req.challenge(), req.url().clone());
            let event = client.sign_event_builder(builder).await?;

            // Get the event ID
            let id = event.id;

            // Get the relay
            let relay = client.relay(req.url()).await?.context("Relay not found")?;

            // Subscribe to notifications
            let mut notifications = relay.notifications();

            // Send the AUTH message
            relay
                .send_msg(ClientMessage::Auth(Cow::Borrowed(&event)))
                .await?;

            log::info!("Sending AUTH event");

            while let Some(notification) = notifications.next().await {
                match notification {
                    RelayNotification::Message { message } => {
                        if let RelayMessage::Ok { event_id, .. } = *message {
                            if id != event_id {
                                continue;
                            }

                            // Get all subscriptions
                            let subscriptions = relay.subscriptions().await;

                            // Re-subscribe to previous subscriptions
                            for (id, filters) in subscriptions.into_iter() {
                                if !filters.is_empty() {
                                    relay.send_msg(ClientMessage::req(id, filters)).await?;
                                }
                            }

                            // Re-send pending events
                            for id in pending_events {
                                if let Some(event) = client.database().event_by_id(&id).await? {
                                    relay.send_event(&event).await?;
                                }
                            }

                            return Ok(());
                        }
                    }
                    RelayNotification::AuthenticationFailed => break,
                    _ => {}
                }
            }

            Err(anyhow!("Authentication failed"))
        })
    }

    /// Respond to an authentication request.
    fn response(&self, req: &Arc<AuthRequest>, window: &Window, cx: &Context<Self>) {
        let settings = AppSettings::global(cx);
        let req = req.clone();
        let challenge = SharedString::from(req.challenge().to_string());

        // Create a task for authentication
        let task = self.auth(&req, cx);

        cx.spawn_in(window, async move |this, cx| {
            let result = task.await;
            let url = req.url();

            this.update_in(cx, |this, window, cx| {
                window.remove_notification1::<AuthNotification>(challenge, cx);

                match result {
                    Ok(_) => {
                        // Clear pending events for the authenticated relay
                        this.clear_pending_events(url, cx);

                        // Save the authenticated relay to automatically authenticate future requests
                        settings.update(cx, |this, cx| {
                            this.add_trusted_relay(url, cx);
                        });

                        window.push_notification(
                            Notification::success(format!(
                                "Relay {} has been authenticated",
                                url.domain().unwrap_or_default()
                            )),
                            cx,
                        );
                    }
                    Err(e) => {
                        window.push_notification(
                            Notification::error(e.to_string()).autohide(false),
                            cx,
                        );
                    }
                }
            })
            .ok();
        })
        .detach();
    }

    /// Push a popup to approve the authentication request.
    fn ask_for_approval(&self, req: &Arc<AuthRequest>, window: &Window, cx: &Context<Self>) {
        let notification = self.notification(req, cx);

        cx.spawn_in(window, async move |_this, cx| {
            cx.update(|window, cx| {
                window.push_notification(notification, cx);
            })
            .ok();
        })
        .detach();
    }

    /// Build a notification for the authentication request.
    fn notification(&self, req: &Arc<AuthRequest>, cx: &Context<Self>) -> Notification {
        let req = req.clone();
        let challenge = SharedString::from(req.challenge.clone());
        let url = SharedString::from(req.url().to_string());
        let entity = cx.entity().downgrade();
        let loading = Rc::new(Cell::new(false));

        Notification::new()
            .id1::<AuthNotification>(challenge)
            .autohide(false)
            .with_type(NotificationType::Info)
            .title("Authentication Required")
            .content(move |_this, _window, cx| {
                v_flex()
                    .gap_2()
                    .child(
                        div()
                            .text_sm()
                            .line_height(relative(1.25))
                            .child(SharedString::from(AUTH_MESSAGE)),
                    )
                    .child(
                        v_flex()
                            .py_1()
                            .px_1p5()
                            .rounded_sm()
                            .text_xs()
                            .bg(cx.theme().muted)
                            .text_color(cx.theme().muted_foreground)
                            .child(url.clone()),
                    )
                    .into_any_element()
            })
            .action(move |_this, _window, _cx| {
                let view = entity.clone();
                let req = req.clone();

                Button::new("approve")
                    .label("Approve")
                    .loading(loading.get())
                    .disabled(loading.get())
                    .on_click({
                        let loading = Rc::clone(&loading);
                        move |_ev, window, cx| {
                            // Set loading state to true
                            loading.set(true);
                            // Process to approve the request
                            view.update(cx, |this, cx| {
                                this.response(&req, window, cx);
                            })
                            .ok();
                        }
                    })
            })
    }
}

struct AuthNotification;
