use crate::ldb::{construct_path, hash, DBConn};
use log::{error, warn};
use lyphedb::{KVList, KeyDirectory, KeyList, LDBNatsMessage, LypheDBCommand, PropagationStrategy};

pub const SITESTORE: &str = "sitestore";

pub const TITLE: &str = "title";
pub const DESCRIPTION: &str = "desc";
pub const KEYWORDS: &str = "keywords";
pub const PAGE_TEXT_RANKING: &str = "ptranks";
pub const PAGE_TEXT_RAW: &str = "textraw";
pub const DAMPING: &str = "damping";

pub async fn add_website(
    db: &DBConn,
    url: &str,
    title: Option<String>,
    description: Option<String>,
    keywords: Option<Vec<String>>,
    page_text_ranking: &[(String, f64)],
    page_text_raw: String,
    damping: f64,
) -> Result<(), ()> {
    let keyurl = hash(url);
    let mut kvs = vec![
        (
            construct_path(&[SITESTORE, &keyurl]).as_bytes().to_vec(),
            url.as_bytes().to_vec(),
        ),
        (
            construct_path(&[SITESTORE, &keyurl, PAGE_TEXT_RANKING])
                .as_bytes()
                .to_vec(),
            rmp_serde::to_vec(page_text_ranking).unwrap(),
        ),
        (
            construct_path(&[SITESTORE, &keyurl, PAGE_TEXT_RAW])
                .as_bytes()
                .to_vec(),
            page_text_raw.as_bytes().to_vec(),
        ),
        (
            construct_path(&[SITESTORE, &keyurl, DAMPING])
                .as_bytes()
                .to_vec(),
            damping.to_be_bytes().to_vec(),
        ),
    ];

    if let Some(title) = title {
        kvs.push((
            construct_path(&[SITESTORE, &keyurl, TITLE]).as_bytes().to_vec(),
            title.as_bytes().to_vec(),
        ))
    }
    if let Some(description) = description {
        kvs.push((
            construct_path(&[SITESTORE, &keyurl, DESCRIPTION])
                .as_bytes()
                .to_vec(),
            description.as_bytes().to_vec(),
        ))
    }
    if let Some(keywords) = keywords {
        kvs.push((
            construct_path(&[SITESTORE, &keyurl, KEYWORDS])
                .as_bytes()
                .to_vec(),
            rmp_serde::to_vec(&keywords).unwrap(),
        ))
    }

    let cmd = LDBNatsMessage::Command(LypheDBCommand::SetKeys(
        KVList { kvs },
        PropagationStrategy::OnRead,
    ));

    match db.query(cmd).await {
        LDBNatsMessage::Success => Ok(()),
        LDBNatsMessage::BadRequest => {
            error!("bad request for add_website");
            Err(())
        }
        LDBNatsMessage::NotFound => {
            error!("not found for add_website");
            Err(())
        }

        _ => {
            warn!("lyphedb sent weird message as response, treating as error");
            Err(())
        }
    }
}

#[derive(Default, Debug, Clone)]
pub struct WebsiteData {
    pub title: Option<String>,
    pub description: Option<String>,
    pub keywords: Option<Vec<String>>,
    pub page_text_ranking: Vec<(String, f64)>,
    pub page_text_raw: String,
    pub damping: f64,
}

pub async fn get_website(db: &DBConn, url: &str) -> Result<WebsiteData, ()> {
    let keyurl = hash(url);
    let keys = [
        construct_path(&[SITESTORE, &keyurl, TITLE]).as_bytes().to_vec(),
        construct_path(&[SITESTORE, &keyurl, DESCRIPTION]).as_bytes().to_vec(),
        construct_path(&[SITESTORE, &keyurl, KEYWORDS]).as_bytes().to_vec(),
        construct_path(&[SITESTORE, &keyurl, PAGE_TEXT_RANKING]).as_bytes().to_vec(),
        construct_path(&[SITESTORE, &keyurl, PAGE_TEXT_RAW]).as_bytes().to_vec(),
        construct_path(&[SITESTORE, &keyurl, DAMPING]).as_bytes().to_vec(),
    ].to_vec();

    let cmd = LDBNatsMessage::Command(LypheDBCommand::GetKeys(KeyList { keys }));

    match db.query(cmd).await {
        LDBNatsMessage::Entries(kvlist) => {
            let mut data = WebsiteData::default();
            for (key, value) in kvlist.kvs {
                let key = String::from_utf8_lossy(&key).to_string();
                match key.as_str() {
                    _ if key.ends_with(TITLE) => {
                        data.title = Some(String::from_utf8_lossy(&value).to_string());
                    }
                    _ if key.ends_with(DESCRIPTION) => {
                        data.description = Some(String::from_utf8_lossy(&value).to_string());
                    }
                    _ if key.ends_with(KEYWORDS) => {
                        let deser = rmp_serde::from_slice::<Vec<String>>(&value);
                        if let Err(e) = deser {
                            error!("bad keywords entry for {}, deser error: {:?}", key, e);
                        } else {
                            data.keywords = Some(deser.unwrap());
                        }
                    }
                    _ if key.ends_with(PAGE_TEXT_RANKING) => {
                        let deser = rmp_serde::from_slice::<Vec<(String, f64)>>(&value);
                        if let Err(e) = deser {
                            error!("bad page_text_ranking entry for {}, deser error: {:?}", key, e);
                        } else {
                            data.page_text_ranking = deser.unwrap();
                        }
                    }
                    _ if key.ends_with(PAGE_TEXT_RAW) => {
                        data.page_text_raw = String::from_utf8_lossy(&value).to_string();
                    }
                    _ if key.ends_with(DAMPING) => {
                        data.damping = f64::from_be_bytes(value.try_into().unwrap_or(0.85f64.to_be_bytes()));
                    }

                    _ => {
                        warn!("encountered weird returned key for get_website");
                    }
                }
            }
            Ok(data)
        }
        LDBNatsMessage::Success => {
            warn!("lyphedb responded with \"success\" to get_website, treating as error");
            Err(())
        }
        LDBNatsMessage::BadRequest => {
            error!("bad request for get_website");
            Err(())
        }
        LDBNatsMessage::NotFound => {
            warn!("not found for get_website");
            Err(())
        }
        _ => {
            warn!("lyphedb sent weird message as response, treating as error");
            Err(())
        }
    }
}
