#[cfg(feature = "sound")]
use crate::subscriptions::{player::PlayerFinish, recorder::RecorderFinish};
use crate::{
    Application, CacheMessage, DATA, Message, PopUp,
    subscriptions::{
        hf_pull::{HFPull, HFPullUpdate},
        message::{MessageGen, MessageGenUpdate},
        ollama_pull::{OllamaPull, OllamaPullUpdate},
    },
};
use iced::{Subscription, Task, widget::markdown, window};
use ochat_common::data::{Data, RequestType};
#[cfg(feature = "sound")]
use ochat_types::generation::tts::TtsResponse;
use ochat_types::{
    chats::{messages::MessageData, previews::Preview},
    generation::text::{ChatQueryData, ChatStreamResult},
    providers::{
        hf::{HFModel, HFPullModelStreamResult, ModelType},
        ollama::{OllamaModelsInfo, OllamaPullModelStreamResult},
    },
    settings::SettingsProvider,
};
use std::collections::HashMap;

#[cfg(feature = "sound")]
use crate::subscriptions::{
    player::{Player, PlayerState, PlayerUpdate},
    recorder::{Recorder, RecorderState, RecorderUpdate},
};

pub mod hf_pull;
pub mod message;
pub mod ollama_pull;

#[cfg(feature = "sound")]
pub mod player;
#[cfg(feature = "sound")]
pub mod recorder;

#[derive(Debug, Clone)]
pub enum SubMessage {
    GenMessage(String, ChatQueryData),
    GeneratingMessage(u32, ChatStreamResult),
    StopGenMessage(u32),
    OllamaPull(OllamaModelsInfo, SettingsProvider),
    OllamaPulling(u32, OllamaPullModelStreamResult),
    OllamaStopPulling(u32),
    HFPull(HFModel, String, ModelType),
    HFPulling(u32, HFPullModelStreamResult),
    HFStopPulling(u32),

    #[cfg(feature = "sound")]
    Record(Option<SettingsProvider>, RecorderFinish),
    #[cfg(feature = "sound")]
    Recording(u32, RecorderState),
    #[cfg(feature = "sound")]
    StopRecording(u32),
    #[cfg(feature = "sound")]
    Play(TtsResponse, PlayerFinish),
    #[cfg(feature = "sound")]
    Playing(PlayerState),
    #[cfg(feature = "sound")]
    StopPlaying,
}

impl SubMessage {
    pub fn handle(self, app: &mut Application) -> Task<Message> {
        match self {
            Self::GenMessage(x, query) => {
                let id = app.subscriptions.counter.clone();
                app.subscriptions.counter += 1;
                app.subscriptions
                    .message_gens
                    .insert(id, MessageGen::new(x, query));

                app.subscriptions
                    .message_gens
                    .get_mut(&id)
                    .unwrap()
                    .start()
                    .map(move |x| {
                        let x = match x {
                            MessageGenUpdate::Generating(x) => x,
                            MessageGenUpdate::Finished(Ok(_)) => ChatStreamResult::Finished,
                            MessageGenUpdate::Finished(Err(e)) => ChatStreamResult::Err(e),
                        };
                        Message::Subscription(SubMessage::GeneratingMessage(id, x))
                    })
            }
            Self::GeneratingMessage(id, ChatStreamResult::Finished) => {
                let message_id = if let Some(x) = app.subscriptions.message_gens.remove(&id) {
                    x.id
                } else {
                    return Task::none();
                };

                let chat_id = app
                    .view_data
                    .home
                    .chats
                    .iter()
                    .find(|x| x.1.messages.contains(&message_id))
                    .map(|x| x.1.chat.id.key().to_string());

                Task::future(async {
                    let req = DATA.read().unwrap().to_request();

                    if let Some(id) = chat_id {
                        match req
                            .make_request::<Preview, ()>(
                                &format!("preview/{}", id),
                                &(),
                                RequestType::Put,
                            )
                            .await
                        {
                            Ok(_) => Message::Cache(CacheMessage::ResetSideBarItems),
                            Err(e) => Message::Err(e),
                        }
                    } else {
                        Message::None
                    }
                })
            }
            Self::GeneratingMessage(id, ChatStreamResult::Generated(result)) => {
                let key = if let Some(x) = app.subscriptions.message_gens.get_mut(&id) {
                    x.progress(ChatStreamResult::Generated(result.clone()));
                    x.id.clone()
                } else {
                    return Task::none();
                };

                let (id, msg) = if let Some(msg) = app.cache.home_shared.messages.0.get_mut(&key) {
                    msg.content = markdown::Content::parse(&result.content);
                    msg.thinking = result
                        .thinking
                        .as_ref()
                        .map(|x| markdown::Content::parse(&x));
                    msg.base.content = result.content;
                    msg.base.thinking = result.thinking;
                    (
                        msg.base.id.key().to_string(),
                        Into::<MessageData>::into(msg.base.clone()),
                    )
                } else {
                    return Task::none();
                };

                Task::future(async move {
                    let req = DATA.read().unwrap().to_request();

                    match req
                        .make_request::<ochat_types::chats::messages::Message, MessageData>(
                            &format!("message/{}", id),
                            &msg,
                            RequestType::Put,
                        )
                        .await
                    {
                        Ok(_) => Message::None,
                        Err(e) => Message::Err(e),
                    }
                })
            }
            Self::GeneratingMessage(id, ChatStreamResult::Generating(result)) => {
                let key = if let Some(x) = app.subscriptions.message_gens.get_mut(&id) {
                    x.progress(ChatStreamResult::Generating(result.clone()));
                    x.id.clone()
                } else {
                    return Task::none();
                };

                if let Some(msg) = app.cache.home_shared.messages.0.get_mut(&key) {
                    msg.base.content.push_str(&result.content);

                    if let (Some(thinking), Some(add)) = (&mut msg.base.thinking, &result.thinking)
                    {
                        thinking.push_str(&add);
                    } else {
                        msg.base.thinking = result.thinking;
                    }

                    msg.content = markdown::Content::parse(&msg.base.content);
                    msg.thinking = msg
                        .base
                        .thinking
                        .as_ref()
                        .map(|x| markdown::Content::parse(&x));
                }

                Task::none()
            }
            Self::GeneratingMessage(id, result) => {
                if let ChatStreamResult::Err(e) = &result {
                    app.add_popup(PopUp::Err(e.to_string()));
                }
                if let Some(x) = app.subscriptions.message_gens.get_mut(&id) {
                    x.progress(result);
                }

                Task::none()
            }
            Self::StopGenMessage(id) => {
                app.subscriptions.message_gens.remove(&id);
                Task::none()
            }
            Self::OllamaPull(data, model) => {
                let id = app.subscriptions.counter.clone();
                app.cache.home_shared.downloads.ollama.insert(id, data);
                app.subscriptions.counter += 1;
                app.subscriptions
                    .ollama_pulls
                    .insert(id, OllamaPull::new(model.provider, model.model));

                app.add_pull_pop_up(id);

                app.subscriptions
                    .ollama_pulls
                    .get_mut(&id)
                    .unwrap()
                    .start()
                    .map(move |x| {
                        let x = match x {
                            OllamaPullUpdate::Pulling(x) => x,
                            OllamaPullUpdate::Finished(Ok(_)) => {
                                OllamaPullModelStreamResult::Finished
                            }
                            OllamaPullUpdate::Finished(Err(e)) => {
                                OllamaPullModelStreamResult::Err(e)
                            }
                        };
                        Message::Subscription(SubMessage::OllamaPulling(id, x))
                    })
            }
            Self::OllamaPulling(id, OllamaPullModelStreamResult::Finished) => {
                let _ = app.subscriptions.ollama_pulls.remove(&id);
                let (url, providers) = {
                    let data = DATA.read().unwrap();
                    (
                        data.instance_url.clone().unwrap(),
                        data.providers
                            .iter()
                            .map(|x| x.id.key().to_string())
                            .collect(),
                    )
                };

                Task::future(async {
                    let jwt = DATA.read().unwrap().jwt.clone();
                    match Data::get_models(&jwt, url, providers).await {
                        Ok(x) => {
                            DATA.write().unwrap().models = x;
                            Message::None
                        }
                        Err(e) => Message::Err(e.to_string()),
                    }
                })
            }
            Self::OllamaPulling(id, result) => {
                if let OllamaPullModelStreamResult::Err(e) = &result {
                    app.add_popup(PopUp::Err(e.to_string()));
                }
                if let Some(x) = app.subscriptions.ollama_pulls.get_mut(&id) {
                    x.progress(result);
                }

                Task::none()
            }
            Self::OllamaStopPulling(id) => {
                app.subscriptions.ollama_pulls.remove(&id);
                app.cache.home_shared.downloads.ollama.remove(&id);
                Task::none()
            }
            Self::HFPull(model, name, model_type) => {
                let id = app.subscriptions.counter.clone();
                app.subscriptions.counter += 1;
                app.subscriptions
                    .hf_pulls
                    .insert(id, HFPull::new(model.id.clone(), name, model_type));
                app.cache.home_shared.downloads.hf.insert(id, model);

                app.add_pull_pop_up(id);

                app.subscriptions
                    .hf_pulls
                    .get_mut(&id)
                    .unwrap()
                    .start()
                    .map(move |x| {
                        let x = match x {
                            HFPullUpdate::Pulling(x) => x,
                            HFPullUpdate::Finished(Ok(_)) => HFPullModelStreamResult::Finished,
                            HFPullUpdate::Finished(Err(e)) => HFPullModelStreamResult::Err(e),
                        };
                        Message::Subscription(SubMessage::HFPulling(id, x))
                    })
            }
            Self::HFPulling(id, HFPullModelStreamResult::Finished) => {
                let _ = app.subscriptions.hf_pulls.remove(&id);
                let (url, providers) = {
                    let data = DATA.read().unwrap();
                    (
                        data.instance_url.clone().unwrap(),
                        data.providers
                            .iter()
                            .map(|x| x.id.key().to_string())
                            .collect(),
                    )
                };

                Task::future(async {
                    let jwt = DATA.read().unwrap().jwt.clone();
                    match Data::get_models(&jwt, url, providers).await {
                        Ok(x) => {
                            DATA.write().unwrap().models = x;
                            Message::None
                        }
                        Err(e) => Message::Err(e.to_string()),
                    }
                })
            }
            Self::HFPulling(id, result) => {
                if let HFPullModelStreamResult::Err(e) = &result {
                    app.add_popup(PopUp::Err(e.to_string()));
                }
                if let Some(x) = app.subscriptions.hf_pulls.get_mut(&id) {
                    x.progress(result);
                }

                Task::none()
            }
            Self::HFStopPulling(id) => {
                app.subscriptions.hf_pulls.remove(&id);
                app.cache.home_shared.downloads.hf.remove(&id);
                Task::none()
            }
            #[cfg(feature = "sound")]
            Self::Play(data, on_finish) => {
                app.subscriptions.player = Some(Player::new(data, on_finish));

                app.subscriptions
                    .player
                    .as_mut()
                    .unwrap()
                    .start()
                    .map(move |x| {
                        let x = match x {
                            PlayerUpdate::Playing(x) => x,
                            PlayerUpdate::Finished(Ok(_)) => PlayerState::Finished,
                            PlayerUpdate::Finished(Err(e)) => PlayerState::Err(e),
                        };
                        Message::Subscription(SubMessage::Playing(x))
                    })
            }
            #[cfg(feature = "sound")]
            Self::Playing(PlayerState::Finished) => {
                let task = if let Some(x) = app
                    .subscriptions
                    .player
                    .as_ref()
                    .map(|x| x.on_finish.clone())
                {
                    x.0(app)
                } else {
                    Task::none()
                };

                app.subscriptions.player = None;

                task
            }
            #[cfg(feature = "sound")]
            Self::Playing(result) => {
                if let PlayerState::Err(e) = &result {
                    app.add_popup(PopUp::Err(e.to_string()));
                }
                if let Some(x) = &mut app.subscriptions.player {
                    x.progress(result);
                }

                Task::none()
            }
            #[cfg(feature = "sound")]
            Self::StopPlaying => {
                let task = if let Some(x) = app
                    .subscriptions
                    .player
                    .as_ref()
                    .map(|x| x.on_finish.clone())
                {
                    x.0(app)
                } else {
                    Task::none()
                };

                app.subscriptions.player = None;

                task
            }
            #[cfg(feature = "sound")]
            Self::Record(provider, on_finish) => {
                let id = app.subscriptions.counter.clone();
                app.subscriptions.counter += 1;
                let model = if provider.is_some() {
                    provider
                } else {
                    app.cache.client_settings.stt_provider.clone()
                };
                app.subscriptions
                    .recordings
                    .insert(id, Recorder::new(on_finish, model));

                app.subscriptions
                    .recordings
                    .get_mut(&id)
                    .unwrap()
                    .start()
                    .map(move |x| {
                        let x = match x {
                            RecorderUpdate::Generating(x) => x,
                            RecorderUpdate::Finished(Ok(_)) => RecorderState::Finished,
                            RecorderUpdate::Finished(Err(e)) => RecorderState::Err(e),
                        };
                        Message::Subscription(SubMessage::Recording(id, x))
                    })
            }
            #[cfg(feature = "sound")]
            Self::Recording(id, RecorderState::Finished) => {
                if let Some(r) = app.subscriptions.recordings.remove(&id) {
                    r.on_finish.0(app, None)
                } else {
                    Task::none()
                }
            }
            #[cfg(feature = "sound")]
            Self::Recording(id, RecorderState::Completed(txt)) => {
                if let Some(r) = app.subscriptions.recordings.remove(&id) {
                    r.on_finish.0(app, Some(txt))
                } else {
                    Task::none()
                }
            }
            #[cfg(feature = "sound")]
            Self::Recording(id, result) => {
                if let RecorderState::Err(e) = &result {
                    app.add_popup(PopUp::Err(e.to_string()));
                }
                if let Some(x) = app.subscriptions.recordings.get_mut(&id) {
                    x.progress(result);
                }

                Task::none()
            }
            #[cfg(feature = "sound")]
            Self::StopRecording(id) => {
                if let Some(r) = app.subscriptions.recordings.remove(&id) {
                    r.on_finish.0(app, None)
                } else {
                    Task::none()
                }
            }
        }
    }
}

#[derive(Debug, Clone, Default)]
pub struct Subscriptions {
    pub counter: u32,
    pub ollama_pulls: HashMap<u32, OllamaPull>,
    pub hf_pulls: HashMap<u32, HFPull>,
    pub message_gens: HashMap<u32, MessageGen>,
    #[cfg(feature = "sound")]
    pub recordings: HashMap<u32, Recorder>,
    #[cfg(feature = "sound")]
    pub player: Option<Player>,
}

impl Subscriptions {
    pub fn get(&self, _app: &Application) -> Subscription<Message> {
        window::resize_events()
            .map(|x| Message::Window(crate::windows::message::WindowMessage::Resize(x.0, x.1)))
    }
}
