#![allow(clippy::todo)]
use futures_util::FutureExt;
use serde_json::json;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::RwLock;

use crate::database::{ErrorKind, MicropubChannel, Result, settings, Storage, StorageError};

#[derive(Clone, Debug, Default)]
pub struct MemoryStorage {
    pub mapping: Arc<RwLock<HashMap<String, serde_json::Value>>>,
    pub channels: Arc<RwLock<HashMap<url::Url, Vec<String>>>>,
}

impl Storage for MemoryStorage {
    async fn new(_url: &url::Url) -> Result<Self> {
        Ok(Self::default())
    }

    async fn categories(&self, _url: &str) -> Result<Vec<String>> {
        unimplemented!()
    }

    async fn post_exists(&self, url: &str) -> Result<bool> {
        return Ok(self.mapping.read().await.contains_key(url));
    }

    async fn get_post(&self, url: &str) -> Result<Option<serde_json::Value>> {
        let mapping = self.mapping.read().await;
        match mapping.get(url) {
            Some(val) => {
                if let Some(new_url) = val["see_other"].as_str() {
                    match mapping.get(new_url) {
                        Some(val) => Ok(Some(val.clone())),
                        None => {
                            drop(mapping);
                            self.mapping.write().await.remove(url);
                            Ok(None)
                        }
                    }
                } else {
                    Ok(Some(val.clone()))
                }
            }
            _ => Ok(None),
        }
    }

    async fn put_post(&self, post: &'_ serde_json::Value, user: &url::Url) -> Result<()> {
        let mapping = &mut self.mapping.write().await;
        let key: &str = match post["properties"]["uid"][0].as_str() {
            Some(uid) => uid,
            None => {
                return Err(StorageError::from_static(
                    ErrorKind::Other,
                    "post doesn't have a UID",
                ))
            }
        };
        mapping.insert(key.to_string(), post.clone());
        if post["properties"]["url"].is_array() {
            for url in post["properties"]["url"]
                .as_array()
                .unwrap()
                .iter()
                .map(|i| i.as_str().unwrap().to_string())
            {
                if url != key {
                    mapping.insert(url, json!({ "see_other": key }));
                }
            }
        }
        if post["type"]
            .as_array()
            .unwrap()
            .iter()
            .any(|i| i == "h-feed")
        {
            // This is a feed. Add it to the channels array if it's not already there.
            println!("{:#}", post);
            self.channels
                .write()
                .await
                .entry(user.clone())
                .or_insert_with(Vec::new)
                .push(key.to_string())
        }
        Ok(())
    }

    async fn update_post(&self, url: &'_ str, update: crate::micropub::MicropubUpdate) -> Result<()> {
        let mut guard = self.mapping.write().await;
        let mut post = guard.get_mut(url).ok_or(StorageError::from_static(ErrorKind::NotFound, "The specified post wasn't found in the database."))?;

        use crate::micropub::MicropubPropertyDeletion;

        let mut add_keys: HashMap<String, Vec<serde_json::Value>> = HashMap::new();
        let mut remove_keys: Vec<String> = vec![];
        let mut remove_values: HashMap<String, Vec<serde_json::Value>> = HashMap::new();

        if let Some(MicropubPropertyDeletion::Properties(delete)) = update.delete {
            remove_keys.extend(delete.iter().cloned());
        } else if let Some(MicropubPropertyDeletion::Values(delete)) = update.delete {
            for (k, v) in delete {
                remove_values
                    .entry(k.to_string())
                    .or_default()
                    .extend(v.clone());
            }
        }
        if let Some(add) = update.add {
            for (k, v) in add {
                add_keys.insert(k.to_string(), v.clone());
            }
        }
        if let Some(replace) = update.replace {
            for (k, v) in replace {
                remove_keys.push(k.to_string());
                add_keys.insert(k.to_string(), v.clone());
            }
        }

        if let Some(props) = post["properties"].as_object_mut() {
            for k in remove_keys {
                props.remove(&k);
            }
        }
        for (k, v) in remove_values {
            let k = &k;
            let props = if k == "children" {
                &mut post
            } else {
                &mut post["properties"]
            };
            v.iter().for_each(|v| {
                if let Some(vec) = props[k].as_array_mut() {
                    if let Some(index) = vec.iter().position(|w| w == v) {
                        vec.remove(index);
                    }
                }
            });
        }
        for (k, v) in add_keys {
            tracing::debug!("Adding k/v to post: {} => {:?}", k, v);
            let props = if k == "children" {
                &mut post
            } else {
                &mut post["properties"]
            };
            if let Some(prop) = props[&k].as_array_mut() {
                if k == "children" {
                    v.into_iter().rev().for_each(|v| prop.insert(0, v));
                } else {
                    prop.extend(v.into_iter());
                }
            } else {
                props[&k] = serde_json::Value::Array(v)
            }
        }

        Ok(())
    }

    async fn get_channels(&self, user: &url::Url) -> Result<Vec<MicropubChannel>> {
        match self.channels.read().await.get(user) {
            Some(channels) => Ok(futures_util::future::join_all(
                channels
                    .iter()
                    .map(|channel| {
                        self.get_post(channel).map(|result| result.unwrap()).map(
                            |post: Option<serde_json::Value>| {
                                post.map(|post| MicropubChannel {
                                    uid: post["properties"]["uid"][0].as_str().unwrap().to_string(),
                                    name: post["properties"]["name"][0]
                                        .as_str()
                                        .unwrap()
                                        .to_string(),
                                })
                            },
                        )
                    })
                    .collect::<Vec<_>>(),
            )
            .await
            .into_iter()
            .flatten()
            .collect::<Vec<_>>()),
            None => Ok(vec![]),
        }
    }

    #[allow(unused_variables)]
    async fn read_feed_with_limit(
        &self,
        url: &'_ str,
        after: Option<&str>,
        limit: usize,
        user: Option<&url::Url>,
    ) -> Result<Option<serde_json::Value>> {
        todo!()
    }

    #[allow(unused_variables)]
    async fn read_feed_with_cursor(
        &self,
        url: &'_ str,
        cursor: Option<&'_ str>,
        limit: usize,
        user: Option<&url::Url>
    ) -> Result<Option<(serde_json::Value, Option<String>)>> {
        todo!()
    }

    async fn delete_post(&self, url: &'_ str) -> Result<()> {
        self.mapping.write().await.remove(url);
        Ok(())
    }

    #[allow(unused_variables)]
    async fn get_setting<S: settings::Setting>(&'_ self, user: &url::Url) -> Result<S> {
        todo!()
    }

    #[allow(unused_variables)]
    async fn set_setting<S: settings::Setting>(&self, user: &url::Url, value: S::Data) -> Result<()> {
        todo!()
    }

    #[allow(unused_variables)]
    async fn add_or_update_webmention(&self, target: &str, mention_type: kittybox_util::MentionType, mention: serde_json::Value) -> Result<()> {
        todo!()
    }

}