use std::{
    collections::HashMap,
    io::Read,
    path::PathBuf,
    sync::{Arc, RwLock},
};

use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
use urlencoding::decode;
use uuid::Uuid;

use crate::types::{AppError, Message, Tabletop, RGB};

#[derive(Debug)]
struct WebsocketClient {
    sender: Option<UnboundedSender<Message>>,
}

#[derive(Debug)]
pub struct AppState {
    pub image_base: PathBuf,
    pub tabletop: Tabletop,
    pub clients: HashMap<String, WebsocketClient>,
}

#[derive(Clone, Debug)]
pub struct Core(pub Arc<RwLock<AppState>>);

impl Core {
    pub fn new() -> Self {
        Self(Arc::new(RwLock::new(AppState {
            image_base: PathBuf::from("/home/savanni/Pictures"),
            tabletop: Tabletop {
                background_color: RGB{ red: 0xca, green: 0xb9, blue: 0xbb },
                background_image: None,
            },
            clients: HashMap::new(),
        })))
    }

    pub fn register_client(&self) -> String {
        let mut state = self.0.write().unwrap();
        let uuid = Uuid::new_v4().simple().to_string();

        let client = WebsocketClient { sender: None };

        state.clients.insert(uuid.clone(), client);
        uuid
    }

    pub fn unregister_client(&self, client_id: String) {
        let mut state = self.0.write().unwrap();
        let _ = state.clients.remove(&client_id);
    }

    pub fn connect_client(&self, client_id: String) -> UnboundedReceiver<Message> {
        let mut state = self.0.write().unwrap();

        match state.clients.get_mut(&client_id) {
            Some(client) => {
                let (tx, rx) = unbounded_channel();
                client.sender = Some(tx);
                rx
            }
            None => {
                unimplemented!();
            }
        }
    }

    pub fn get_file(&self, file_name: String) -> Vec<u8> {
        let file_name = decode(&file_name).expect("UTF-8");

        let mut full_path = self.0.read().unwrap().image_base.clone();
        full_path.push(file_name.to_string());


        let mut content: Vec<u8> = Vec::new();
        let mut file = std::fs::File::open(&full_path).unwrap();
        file.read_to_end(&mut content).unwrap();
        content
    }

    pub fn available_images(&self) -> Vec<String> {
        std::fs::read_dir(&self.0.read().unwrap().image_base)
            .unwrap()
            .filter_map(|entry| match entry {
                Ok(entry_) => match mime_guess::from_path(entry_.path()).first() {
                    Some(mime) if mime.type_() == mime::IMAGE => Some(
                        entry_
                            .path()
                            .file_name()
                            .and_then(|filename| filename.to_str())
                            .and_then(|filename| Some(filename.to_owned()))
                            .unwrap(),
                    ),
                    _ => None,
                },
                Err(_) => None,
            })
            .collect()
    }

    pub fn set_background_image(&self, path: String) -> Result<(), AppError> {
        let tabletop = {
            let mut state = self.0.write().unwrap();
            state.tabletop.background_image = Some(path.clone());
            state.tabletop.clone()
        };
        self.publish(Message::UpdateTabletop(tabletop));
        Ok(())
    }

    pub fn publish(&self, message: Message) {
        let state = self.0.read().unwrap();

        state.clients.values().for_each(|client| {
            if let Some(ref sender) = client.sender {
                let _ = sender.send(message.clone());
            }
        });
    }
}