use std::collections::HashMap;
use std::sync::Arc;
use std::time::Duration;

use tokio::sync::{mpsc, RwLock};
use tokio::time;

use super::WsSubscriptionBody;
use crate::nuts::nut17::Kind;
use crate::nuts::{nut01, nut05, nut07, nut23, CheckStateRequest, NotificationPayload};
use crate::pub_sub::SubId;
use crate::wallet::MintConnector;
use crate::Wallet;

#[derive(Debug, Hash, PartialEq, Eq)]
enum UrlType {
    Mint(String),
    Melt(String),
    PublicKey(nut01::PublicKey),
}

#[derive(Debug, Eq, PartialEq)]
enum AnyState {
    MintQuoteState(nut23::QuoteState),
    MeltQuoteState(nut05::QuoteState),
    PublicKey(nut07::State),
    Empty,
}

type SubscribedTo = HashMap<UrlType, (mpsc::Sender<NotificationPayload<String>>, SubId, AnyState)>;

async fn convert_subscription(
    sub_id: SubId,
    subscriptions: &Arc<RwLock<HashMap<SubId, WsSubscriptionBody>>>,
    subscribed_to: &mut SubscribedTo,
) -> Option<()> {
    let subscription = subscriptions.read().await;
    let sub = subscription.get(&sub_id)?;
    tracing::debug!("New subscription: {:?}", sub);
    match sub.1.kind {
        Kind::Bolt11MintQuote => {
            for id in sub.1.filters.iter().map(|id| UrlType::Mint(id.clone())) {
                subscribed_to.insert(id, (sub.0.clone(), sub.1.id.clone(), AnyState::Empty));
            }
        }
        Kind::Bolt11MeltQuote => {
            for id in sub.1.filters.iter().map(|id| UrlType::Melt(id.clone())) {
                subscribed_to.insert(id, (sub.0.clone(), sub.1.id.clone(), AnyState::Empty));
            }
        }
        Kind::ProofState => {
            for id in sub
                .1
                .filters
                .iter()
                .map(|id| nut01::PublicKey::from_hex(id).map(UrlType::PublicKey))
            {
                match id {
                    Ok(id) => {
                        subscribed_to
                            .insert(id, (sub.0.clone(), sub.1.id.clone(), AnyState::Empty));
                    }
                    Err(err) => {
                        tracing::error!("Error parsing public key: {:?}. Subscription ignored, will never yield any result", err);
                    }
                }
            }
        }
    }

    Some(())
}

#[inline]
pub async fn http_main<S: IntoIterator<Item = SubId>>(
    initial_state: S,
    http_client: Arc<dyn MintConnector + Send + Sync>,
    subscriptions: Arc<RwLock<HashMap<SubId, WsSubscriptionBody>>>,
    mut new_subscription_recv: mpsc::Receiver<SubId>,
    mut on_drop: mpsc::Receiver<SubId>,
    _wallet: Arc<Wallet>,
) {
    let mut interval = time::interval(Duration::from_secs(2));
    let mut subscribed_to = HashMap::<UrlType, (mpsc::Sender<_>, _, AnyState)>::new();

    for sub_id in initial_state {
        convert_subscription(sub_id, &subscriptions, &mut subscribed_to).await;
    }

    loop {
        tokio::select! {
            _ = interval.tick() => {
                for (url, (sender, _, last_state)) in subscribed_to.iter_mut() {
                    tracing::debug!("Polling: {:?}", url);
                    match url {
                        UrlType::Mint(id) => {

                            let response = http_client.get_mint_quote_status(id).await;
                            if let Ok(response) = response {
                                if *last_state == AnyState::MintQuoteState(response.state) {
                                    continue;
                                }
                                *last_state = AnyState::MintQuoteState(response.state);
                                if let Err(err) = sender.try_send(NotificationPayload::MintQuoteBolt11Response(response)) {
                                    tracing::error!("Error sending mint quote response: {:?}", err);
                                }
                            }
                        }
                        UrlType::Melt(id) => {

                            let response = http_client.get_melt_quote_status(id).await;
                            if let Ok(response) = response {
                                if *last_state == AnyState::MeltQuoteState(response.state) {
                                    continue;
                                }
                                *last_state = AnyState::MeltQuoteState(response.state);
                                if let Err(err) =  sender.try_send(NotificationPayload::MeltQuoteBolt11Response(response)) {
                                    tracing::error!("Error sending melt quote response: {:?}", err);
                                }
                            }
                        }
                        UrlType::PublicKey(id) => {
                            let responses = http_client.post_check_state(CheckStateRequest {
                                ys: vec![*id],
                            }
                            ).await;
                            if let Ok(mut responses) = responses {
                                let response = if let Some(state) = responses.states.pop() {
                                    state
                                } else {
                                    continue;
                                };

                                if *last_state == AnyState::PublicKey(response.state) {
                                    continue;
                                }
                                *last_state = AnyState::PublicKey(response.state);
                                if let Err(err) = sender.try_send(NotificationPayload::ProofState(response)) {
                                    tracing::error!("Error sending proof state response: {:?}", err);
                                }
                            }
                        }
                    }
                }
            }
            Some(subid) = new_subscription_recv.recv() => {
                convert_subscription(subid, &subscriptions, &mut subscribed_to).await;
            }
            Some(id) = on_drop.recv() => {
                subscribed_to.retain(|_, (_, sub_id, _)| *sub_id != id);
            }
        }
    }
}
