use crate::{Connection, Error, Subscription};
use nostr_rs_storage_base::Storage;
use nostr_rs_types::{
    relayer,
    types::{Event, SubscriptionId},
    Request, Response,
};
use parking_lot::{RwLock, RwLockReadGuard};
use std::{collections::HashMap, ops::Deref, sync::Arc};
use tokio::sync::mpsc;
#[allow(unused_imports)]
use tokio::{
    net::TcpStream,
    sync::mpsc::{channel, Receiver, Sender},
};

type SubId = u128;

type Subscriptions = HashMap<SubId, (SubscriptionId, Sender<Response>)>;

pub struct Relayer<T: Storage> {
    /// Storage engine, if provided the services are going to persisted in disk,
    /// otherwise all the messages are going to be ephemeral, making this
    /// relayer just a dumb proxy (that can be useful for privacy) but it won't
    /// be able to perform any optimization like prefetching content while offline
    storage: Option<T>,
    /// Keeps a map between the internal subscription ID and the subscription
    /// type. One subscription ID may have multiple subscription types.
    ///
    /// Each connection keeps a list of the subscription ID provided by the user
    /// (String) and the internal, globally recognized subscription ID which is
    /// internal (SubId)
    subscriptions_ids_index: RwLock<HashMap<SubId, Vec<Subscription>>>,
    /// Each subscription type that is active has a list of subscriptions.
    ///
    /// A single REQ can be subscribed to multiple subscription types, specially
    /// when it is translated in OR filters. It is designed this way to allow a
    /// fast iteration and match quickly filters.
    subscriptions: RwLock<HashMap<Subscription, RwLock<Subscriptions>>>,
    clients: RwLock<HashMap<u128, Connection>>,
    #[allow(dead_code)]
    sender: Sender<(u128, Request)>,
}

impl<T: Storage> Relayer<T> {
    pub fn new(storage: Option<T>) -> (Arc<Self>, Receiver<(u128, Request)>) {
        let (sender, receiver) = channel(100_000);
        (
            Arc::new(Self {
                storage,
                subscriptions: RwLock::new(HashMap::new()),
                subscriptions_ids_index: RwLock::new(HashMap::new()),
                clients: RwLock::new(HashMap::new()),
                sender,
            }),
            receiver,
        )
    }

    /// Returns a reference to the internal database
    pub fn get_db(&self) -> &Option<T> {
        &self.storage
    }

    pub async fn add_connection(
        &self,
        disconnection_notify: Option<mpsc::Sender<u128>>,
        stream: TcpStream,
    ) -> Result<u128, Error> {
        let client = Connection::new(self.sender.clone(), disconnection_notify, stream).await?;
        let id = client.conn_id;
        let mut clients = self.clients.write();
        clients.insert(id, client);

        Ok(id)
    }

    fn recv_request_from_client(
        &self,
        connection: &Connection,
        request: Request,
    ) -> Result<Option<Request>, Error> {
        match &request {
            Request::Event(event) => {
                self.store_and_broadcast_local_event(event.deref());
            }
            Request::Request(request) => {
                // Create subscription
                let (sub_id, receiver) =
                    connection.create_subscription(request.subscription_id.deref().to_owned());
                let mut sub_index = self.subscriptions_ids_index.write();
                let mut subscriptions = self.subscriptions.write();
                if let Some(prev_subs) = sub_index.remove(&sub_id) {
                    // remove any previous subscriptions
                    prev_subs.iter().for_each(|index| {
                        if let Some(subscriptions) = subscriptions.get_mut(index) {
                            subscriptions.write().remove(&sub_id);
                        }
                    });
                }
                sub_index.insert(
                    sub_id,
                    Subscription::from_filters(&request.filters)
                        .into_iter()
                        .map(|index| {
                            subscriptions
                                .entry(index.clone())
                                .or_insert_with(|| RwLock::new(HashMap::new()))
                                .write()
                                .insert(
                                    sub_id,
                                    (request.subscription_id.clone(), receiver.clone()),
                                );
                            index
                        })
                        .collect::<Vec<_>>(),
                );

                drop(subscriptions);
                drop(sub_index);

                if let Some(storage) = self.storage.as_ref() {
                    // Sent all events that match the filter that are stored in our database
                    for filter in request.filters.clone().into_iter() {
                        storage.get_by_filter(filter)?.for_each(|event| {
                            if let Ok(event) = event {
                                let _ = connection.send(
                                    relayer::Event {
                                        subscription_id: request.subscription_id.clone(),
                                        event,
                                    }
                                    .into(),
                                );
                            }
                        });
                    }
                }

                let _ = connection
                    .send(relayer::EndOfStoredEvents(request.subscription_id.clone()).into());
            }
            Request::Close(close) => {
                if let Some(id) = connection.get_subscription_id(&close.0) {
                    let mut subscriptions = self.subscriptions_ids_index.write();
                    if let Some(indexes) = subscriptions.remove(&id) {
                        let mut subscriptions = self.subscriptions.write();
                        indexes.iter().for_each(|index| {
                            if let Some(subscriptions) = subscriptions.get_mut(index) {
                                subscriptions.write().remove(&id);
                            }
                        });
                    }
                }
            }
        };

        Ok(Some(request))
    }

    pub async fn recv(
        &self,
        receiver: &mut Receiver<(u128, Request)>,
    ) -> Result<Option<Request>, Error> {
        let (conn_id, request) = if let Some(request) = receiver.recv().await {
            request
        } else {
            return Ok(None);
        };
        let connections = self.clients.read();
        let connection = connections
            .get(&conn_id)
            .ok_or(Error::UnknownConnection(conn_id))?;

        self.recv_request_from_client(connection, request)
    }

    pub fn send_to_conn(&self, conn_id: u128, response: Response) -> Result<(), Error> {
        let connections = self.clients.read();
        let connection = connections
            .get(&conn_id)
            .ok_or(Error::UnknownConnection(conn_id))?;

        connection.send(response)
    }

    #[inline]
    fn broadcast_to_subscribers(subscriptions: RwLockReadGuard<Subscriptions>, event: &Event) {
        for (_, receiver) in subscriptions.iter() {
            let _ = receiver.1.try_send(
                relayer::Event {
                    subscription_id: receiver.0.clone(),
                    event: event.clone(),
                }
                .into(),
            );
        }
    }

    #[inline]
    pub fn store_and_broadcast_local_event(&self, event: &Event) {
        if let Some(storage) = self.storage.as_ref() {
            let _ = storage.store_local_event(event);
        }
        let subscriptions = self.subscriptions.read();

        for subscription_type in Subscription::from_event(event) {
            if let Some(subscribers) = subscriptions.get(&subscription_type) {
                Self::broadcast_to_subscribers(subscribers.read(), event);
            }
        }
    }

    #[inline]
    pub fn store_and_broadcast(&self, event: &Event) {
        if let Some(storage) = self.storage.as_ref() {
            let _ = storage.store(event);
        }
        let subscriptions = self.subscriptions.read();

        for subscription_type in Subscription::from_event(event) {
            if let Some(subscribers) = subscriptions.get(&subscription_type) {
                Self::broadcast_to_subscribers(subscribers.read(), event);
            }
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::get_id;
    use nostr_rs_rocksdb::RocksDb;
    use nostr_rs_types::Request;

    fn get_db(prefill: bool) -> RocksDb {
        let db = RocksDb::new(format!("/tmp/db/{}", get_id())).expect("db");
        if prefill {
            let events = include_str!("../tests/events.json")
                .lines()
                .map(|line| serde_json::from_str(&line).expect("valid"))
                .collect::<Vec<Event>>();

            for event in events {
                assert!(db.store(&event).expect("valid"));
            }
        }
        db
    }

    #[tokio::test]
    async fn serve_listener_from_local_db() {
        let request: Request = serde_json::from_str("[
                \"REQ\",\"1298169700973717\",
                {\"authors\":[\"39a7d06e824c0c2523bedb93f0cef84245e4401fee03b6257a1c6dfd18b57efb\"],\"since\":1681928304},
                {\"#p\":[\"39a7d06e824c0c2523bedb93f0cef84245e4401fee03b6257a1c6dfd18b57efb\"],\"kinds\":[1,3,6,7,9735],\"since\":1681928304},
                {\"#p\":[\"39a7d06e824c0c2523bedb93f0cef84245e4401fee03b6257a1c6dfd18b57efb\"],\"kinds\":[4]},
                {\"authors\":[\"39a7d06e824c0c2523bedb93f0cef84245e4401fee03b6257a1c6dfd18b57efb\"],\"kinds\":[4]},
                {\"#e\":[\"2e72250d80e9b3fd30230b3db3ed7d22f15d266ed345c36700b01ec153c9e28a\",\"a5e3369c43daf2675ecbce18831e5f4e07db0d4dde0ef4f5698e645e4c46eed1\"],\"kinds\":[1,6,7,9735]}
            ]").expect("valid object");
        let (relayer, _) = Relayer::new(Some(get_db(true)));
        let (connection, mut recv) = Connection::new_for_test();
        let _ = relayer.recv_request_from_client(&connection, request);
        // ev1
        assert_eq!(
            "9508850d7ddc8ef58c8b392236c49d472dc23fa11f4e73eb5475dfb099ddff42",
            recv.try_recv()
                .expect("valid")
                .as_event()
                .expect("event")
                .event
                .id
                .to_string()
        );
        // ev3
        assert_eq!(
            "e862fe23daf52ab09b36a37fa91ca3743e0c323e630e8627891212ca147c2da9",
            recv.try_recv()
                .expect("valid")
                .as_event()
                .expect("event")
                .event
                .id
                .to_string()
        );
        // ev2
        assert_eq!(
            "2e72250d80e9b3fd30230b3db3ed7d22f15d266ed345c36700b01ec153c9e28a",
            recv.try_recv()
                .expect("valid")
                .as_event()
                .expect("event")
                .event
                .id
                .to_string()
        );
        // eod
        assert!(recv
            .try_recv()
            .expect("valid")
            .as_end_of_stored_events()
            .is_some());

        assert!(recv.try_recv().is_err());
    }

    #[tokio::test]
    async fn server_listener_real_time() {
        let request: Request = serde_json::from_str("[\"REQ\",\"1298169700973717\",{\"authors\":[\"39a7d06e824c0c2523bedb93f0cef84245e4401fee03b6257a1c6dfd18b57efb\"],\"since\":1681939304},{\"#p\":[\"39a7d06e824c0c2523bedb93f0cef84245e4401fee03b6257a1c6dfd18b57efb\"],\"kinds\":[1,3,6,7,9735],\"since\":1681939304},{\"#p\":[\"39a7d06e824c0c2523bedb93f0cef84245e4401fee03b6257a1c6dfd18b57efb\"],\"kinds\":[4]},{\"authors\":[\"39a7d06e824c0c2523bedb93f0cef84245e4401fee03b6257a1c6dfd18b57efb\"],\"kinds\":[4]},{\"#e\":[\"2e72250d80e9b3fd30230b3db3ed7d22f15d266ed345c36700b01ec153c9e28a\",\"a5e3369c43daf2675ecbce18831e5f4e07db0d4dde0ef4f5698e645e4c46eed1\"],\"kinds\":[1,6,7,9735]}]").expect("valid object");
        let (relayer, _) = Relayer::new(Some(get_db(false)));
        let (connection, mut recv) = Connection::new_for_test();
        let _ = relayer.recv_request_from_client(&connection, request);
        // eod
        assert!(recv
            .try_recv()
            .expect("valid")
            .as_end_of_stored_events()
            .is_some());

        // It is empty
        assert!(recv.try_recv().is_err());

        let new_event: Request = serde_json::from_str(r#"["EVENT", {"kind":1,"content":"Pong","tags":[["e","9508850d7ddc8ef58c8b392236c49d472dc23fa11f4e73eb5475dfb099ddff42","","root"],["e","2e72250d80e9b3fd30230b3db3ed7d22f15d266ed345c36700b01ec153c9e28a","","reply"],["p","39a7d06e824c0c2523bedb93f0cef84245e4401fee03b6257a1c6dfd18b57efb"],["p","ee7202ad91459e013bfef263c59e47deb0163a5e7651b026673765488bfaf102"]],"created_at":1681938616,"pubkey":"a42007e33cfa25673b26f46f39df039aa6003258a68dc88f1f1e0447607aedb3","id":"e862fe23daf52ab09b36a37fa91ca3743e0c323e630e8627891212ca147c2da9","sig":"9036150a6c8a32933cffcc42aec4d2109a22e9f10d1c3860c0435a925e6386babb7df5c95fcf68c8ed6a9726a1f07225af663d0b068eb555014130aad21674fc","meta":{"revision":0,"created":1681939266488,"version":0},"$loki":108}]"#).expect("value");

        relayer
            .recv_request_from_client(&connection, new_event)
            .expect("process event");

        // It is not empty
        assert!(recv.try_recv().is_ok());
    }
}
