use std::collections::HashMap;

use anyhow::Error;
use common::TimestampExt;
use gpui::prelude::FluentBuilder;
use gpui::{
    App, AppContext, Context, Div, Entity, InteractiveElement, IntoElement, ParentElement, Render,
    SharedString, Styled, Subscription, Task, Window, div, px, relative, uniform_list,
};
use instant::Duration;
use nostr_sdk::prelude::*;
use person::{Person, PersonRegistry, shorten_pubkey};
use smallvec::{SmallVec, smallvec};
use state::{BOOTSTRAP_RELAYS, NostrAddress, NostrRegistry, TIMEOUT};
use theme::ActiveTheme;
use ui::avatar::Avatar;
use ui::button::{Button, ButtonVariants};
use ui::indicator::Indicator;
use ui::{Disableable, Icon, IconName, Sizable, StyledExt, WindowExtension, h_flex, v_flex};

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

/// Screening
pub struct Screening {
    /// Public Key of the person being screened.
    public_key: PublicKey,

    /// Whether the person's address is verified.
    verified: bool,

    /// Whether the person is followed by current user.
    followed: bool,

    /// Last time the person was active.
    last_active: Option<Timestamp>,

    /// All mutual contacts of the person being screened.
    mutual_contacts: Vec<PublicKey>,

    /// Async tasks
    tasks: SmallVec<[Task<()>; 3]>,

    /// Subscriptions
    _subscriptions: SmallVec<[Subscription; 1]>,
}

impl Screening {
    pub fn new(public_key: PublicKey, window: &mut Window, cx: &mut Context<Self>) -> Self {
        let mut subscriptions = smallvec![];

        subscriptions.push(cx.on_release_in(window, move |this, window, cx| {
            this.tasks.clear();
            window.close_all_modals(cx);
        }));

        cx.defer_in(window, |this, _window, cx| {
            this.check_contact(cx);
            this.check_wot(cx);
            this.check_last_activity(cx);
            this.verify_identifier(cx);
        });

        Self {
            public_key,
            verified: false,
            followed: false,
            last_active: None,
            mutual_contacts: vec![],
            tasks: smallvec![],
            _subscriptions: subscriptions,
        }
    }

    fn check_contact(&mut self, cx: &mut Context<Self>) {
        let nostr = NostrRegistry::global(cx);
        let client = nostr.read(cx).client();
        let public_key = self.public_key;

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

        let task: Task<Result<bool, Error>> = cx.background_spawn(async move {
            // Check if user is in contact list
            let contacts = client.database().contacts_public_keys(current_user).await;
            let followed = contacts.unwrap_or_default().contains(&public_key);

            Ok(followed)
        });

        self.tasks.push(cx.spawn(async move |this, cx| {
            let result = task.await.unwrap_or(false);

            this.update(cx, |this, cx| {
                this.followed = result;
                cx.notify();
            })
            .ok();
        }));
    }

    fn check_wot(&mut self, cx: &mut Context<Self>) {
        let nostr = NostrRegistry::global(cx);
        let client = nostr.read(cx).client();
        let public_key = self.public_key;

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

        let task: Task<Result<Vec<PublicKey>, Error>> = cx.background_spawn(async move {
            // Check mutual contacts
            let filter = Filter::new().kind(Kind::ContactList).pubkey(public_key);
            let mut mutual_contacts = vec![];

            if let Ok(events) = client.database().query(filter).await {
                for event in events.into_iter().filter(|ev| ev.pubkey != current_user) {
                    mutual_contacts.push(event.pubkey);
                }
            }

            Ok(mutual_contacts)
        });

        self.tasks.push(cx.spawn(async move |this, cx| {
            match task.await {
                Ok(contacts) => {
                    this.update(cx, |this, cx| {
                        this.mutual_contacts = contacts;
                        cx.notify();
                    })
                    .ok();
                }
                Err(e) => {
                    log::error!("Failed to fetch mutual contacts: {}", e);
                }
            };
        }));
    }

    fn check_last_activity(&mut self, cx: &mut Context<Self>) {
        let nostr = NostrRegistry::global(cx);
        let client = nostr.read(cx).client();
        let public_key = self.public_key;

        let task: Task<Option<Timestamp>> = cx.background_spawn(async move {
            let filter = Filter::new().author(public_key).limit(1);
            let mut activity: Option<Timestamp> = None;

            // Construct target for subscription
            let target: HashMap<&str, Vec<Filter>> = BOOTSTRAP_RELAYS
                .into_iter()
                .map(|relay| (relay, vec![filter.clone()]))
                .collect();

            if let Ok(mut stream) = client
                .stream_events(target)
                .timeout(Duration::from_secs(TIMEOUT))
                .await
            {
                while let Some((_url, event)) = stream.next().await {
                    if let Ok(event) = event {
                        activity = Some(event.created_at);
                    }
                }
            }

            activity
        });

        self.tasks.push(cx.spawn(async move |this, cx| {
            let result = task.await;

            this.update(cx, |this, cx| {
                this.last_active = result;
                cx.notify();
            })
            .ok();
        }));
    }

    fn verify_identifier(&mut self, cx: &mut Context<Self>) {
        let http_client = cx.http_client();
        let public_key = self.public_key;

        // Skip if the user doesn't have a NIP-05 identifier
        let Some(address) = self.address(cx) else {
            return;
        };

        let task: Task<Result<bool, Error>> =
            cx.background_spawn(async move { address.verify(&http_client, &public_key).await });

        self.tasks.push(cx.spawn(async move |this, cx| {
            let result = task.await.unwrap_or(false);

            this.update(cx, |this, cx| {
                this.verified = result;
                cx.notify();
            })
            .ok();
        }));
    }

    fn profile(&self, cx: &Context<Self>) -> Person {
        let persons = PersonRegistry::global(cx);
        persons.read(cx).get(&self.public_key, cx)
    }

    fn address(&self, cx: &Context<Self>) -> Option<Nip05Address> {
        self.profile(cx)
            .metadata()
            .nip05
            .and_then(|addr| Nip05Address::parse(&addr).ok())
    }

    fn open_njump(&mut self, _window: &mut Window, cx: &mut Context<Self>) {
        let Ok(bech32) = self.profile(cx).public_key().to_bech32();
        cx.open_url(&format!("https://njump.me/{bech32}"));
    }

    fn report(&mut self, window: &mut Window, cx: &mut Context<Self>) {
        let nostr = NostrRegistry::global(cx);
        let client = nostr.read(cx).client();
        let signer = nostr.read(cx).signer();
        let public_key = self.public_key;

        let task: Task<Result<(), Error>> = cx.background_spawn(async move {
            let tag = Nip56Tag::PublicKey {
                public_key,
                report: Report::Impersonation,
            }
            .to_tag();

            let event = EventBuilder::new(Kind::Reporting, "")
                .tag(tag)
                .finalize_async(&signer)
                .await?;

            // Send the report to the public relays
            client.send_event(&event).to(BOOTSTRAP_RELAYS).await?;

            Ok(())
        });

        self.tasks.push(cx.spawn_in(window, async move |_, cx| {
            if task.await.is_ok() {
                cx.update(|window, cx| {
                    window.close_modal(cx);
                    window.push_notification("Report submitted successfully", cx);
                })
                .ok();
            }
        }));
    }

    fn mutual_contacts(&mut self, window: &mut Window, cx: &mut Context<Self>) {
        let contacts = self.mutual_contacts.clone();

        window.open_modal(cx, move |this, _window, _cx| {
            let contacts = contacts.clone();
            let total = contacts.len();

            this.title("Mutual contacts").child(
                v_flex().gap_1().pb_2().child(
                    uniform_list("contacts", total, move |range, _window, cx| {
                        let persons = PersonRegistry::global(cx);
                        let mut items = Vec::with_capacity(total);

                        for ix in range {
                            let Some(contact) = contacts.get(ix) else {
                                continue;
                            };
                            let profile = persons.read(cx).get(contact, cx);

                            items.push(
                                h_flex()
                                    .h_11()
                                    .w_full()
                                    .px_2()
                                    .gap_1p5()
                                    .rounded(cx.theme().radius)
                                    .text_sm()
                                    .hover(|this| this.bg(cx.theme().elevated_surface_background))
                                    .child(Avatar::new(profile.avatar()).small())
                                    .child(profile.name()),
                            );
                        }

                        items
                    })
                    .h(px(300.)),
                ),
            )
        });
    }
}

impl Render for Screening {
    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
        const CONTACT: &str = "This person is one of your contacts.";
        const NOT_CONTACT: &str = "This person is not one of your contacts.";
        const NO_ACTIVITY: &str = "This person hasn't had any activity.";
        const RELAY_INFO: &str = "Only checked on public relays; may be inaccurate.";
        const NO_MUTUAL: &str = "You don't have any mutual contacts.";
        const NIP05_MATCH: &str = "The address matches the user's public key.";
        const NIP05_NOT_MATCH: &str = "The address does not match the user's public key.";
        const NO_NIP05: &str = "This person has not set up their friendly address";

        let profile = self.profile(cx);
        let shorten_pubkey = shorten_pubkey(self.public_key, 8);

        let last_active = self.last_active.map(|_| true);
        let mutuals = self.mutual_contacts.len();
        let mutuals_str = format!("You have {} mutual contacts with this person.", mutuals);

        v_flex()
            .gap_4()
            .child(
                v_flex()
                    .gap_3()
                    .items_center()
                    .justify_center()
                    .text_center()
                    .child(Avatar::new(profile.avatar()).large())
                    .child(
                        div()
                            .font_semibold()
                            .line_height(relative(1.25))
                            .child(profile.name()),
                    ),
            )
            .child(
                h_flex()
                    .gap_3()
                    .child(
                        h_flex()
                            .p_1()
                            .flex_1()
                            .h_7()
                            .justify_center()
                            .rounded_full()
                            .bg(cx.theme().elevated_surface_background)
                            .text_sm()
                            .truncate()
                            .text_ellipsis()
                            .text_center()
                            .line_height(relative(1.))
                            .child(shorten_pubkey),
                    )
                    .child(
                        h_flex()
                            .gap_1()
                            .child(
                                Button::new("njump")
                                    .icon(IconName::Link)
                                    .label("njump.me")
                                    .secondary()
                                    .small()
                                    .rounded()
                                    .on_click(cx.listener(move |this, _e, window, cx| {
                                        this.open_njump(window, cx);
                                    })),
                            )
                            .child(
                                Button::new("report")
                                    .tooltip("Report as a scam or impostor")
                                    .icon(IconName::Warning)
                                    .small()
                                    .warning()
                                    .rounded()
                                    .on_click(cx.listener(move |this, _e, window, cx| {
                                        this.report(window, cx);
                                    })),
                            ),
                    ),
            )
            .child(
                v_flex()
                    .gap_3()
                    .child(
                        h_flex()
                            .items_start()
                            .gap_2()
                            .text_sm()
                            .child(status_badge(Some(self.followed), cx))
                            .child(
                                v_flex().text_sm().child("Contact").child(
                                    div()
                                        .line_clamp(1)
                                        .text_color(cx.theme().text_muted)
                                        .child({
                                            if self.followed {
                                                SharedString::from(CONTACT)
                                            } else {
                                                SharedString::from(NOT_CONTACT)
                                            }
                                        }),
                                ),
                            ),
                    )
                    .child(
                        h_flex()
                            .items_start()
                            .gap_2()
                            .text_sm()
                            .child(status_badge(last_active, cx))
                            .child(
                                v_flex()
                                    .text_sm()
                                    .child(
                                        h_flex()
                                            .gap_0p5()
                                            .child("Activity on Public Relays")
                                            .child(
                                                Button::new("active")
                                                    .icon(IconName::Info)
                                                    .xsmall()
                                                    .ghost()
                                                    .rounded()
                                                    .tooltip(RELAY_INFO),
                                            ),
                                    )
                                    .child(
                                        div()
                                            .w_full()
                                            .line_clamp(1)
                                            .text_color(cx.theme().text_muted)
                                            .map(|this| {
                                                if let Some(t) = self.last_active {
                                                    this.child(SharedString::from(format!(
                                                        "Last active: {}.",
                                                        t.to_human_time()
                                                    )))
                                                } else {
                                                    this.child(SharedString::from(NO_ACTIVITY))
                                                }
                                            }),
                                    ),
                            ),
                    )
                    .child(
                        h_flex()
                            .items_start()
                            .gap_2()
                            .child(status_badge(Some(self.verified), cx))
                            .child(
                                v_flex()
                                    .text_sm()
                                    .child({
                                        if let Some(addr) = self.address(cx) {
                                            SharedString::from(format!("{} validation", addr))
                                        } else {
                                            SharedString::from(
                                                "Friendly Address (NIP-05) validation",
                                            )
                                        }
                                    })
                                    .child(
                                        div()
                                            .line_clamp(1)
                                            .text_color(cx.theme().text_muted)
                                            .child({
                                                if self.address(cx).is_some() {
                                                    if self.verified {
                                                        SharedString::from(NIP05_MATCH)
                                                    } else {
                                                        SharedString::from(NIP05_NOT_MATCH)
                                                    }
                                                } else {
                                                    SharedString::from(NO_NIP05)
                                                }
                                            }),
                                    ),
                            ),
                    )
                    .child(
                        h_flex()
                            .items_start()
                            .gap_2()
                            .child(status_badge(Some(mutuals > 0), cx))
                            .child(
                                h_flex()
                                    .text_sm()
                                    .child(
                                        div()
                                            .line_clamp(1)
                                            .text_color(cx.theme().text_muted)
                                            .child({
                                                if mutuals > 0 {
                                                    SharedString::from(mutuals_str)
                                                } else {
                                                    SharedString::from(NO_MUTUAL)
                                                }
                                            }),
                                    )
                                    .child(
                                        Button::new("mutuals")
                                            .icon(IconName::Info)
                                            .xsmall()
                                            .ghost()
                                            .rounded()
                                            .disabled(mutuals == 0)
                                            .on_click(cx.listener(move |this, _, window, cx| {
                                                this.mutual_contacts(window, cx);
                                            })),
                                    ),
                            ),
                    ),
            )
    }
}

fn status_badge(status: Option<bool>, cx: &App) -> Div {
    h_flex()
        .size_6()
        .justify_center()
        .flex_shrink_0()
        .map(|this| {
            if let Some(status) = status {
                this.child(Icon::new(IconName::CheckCircle).small().text_color({
                    if status {
                        cx.theme().icon_accent
                    } else {
                        cx.theme().icon_muted
                    }
                }))
            } else {
                this.child(Indicator::new().small())
            }
        })
}
