mod article_model;
mod error;

use self::article_model::MercuryArticle;
use self::error::{MercuryParserError, MercuryParserErrorKind};
use crate::models::Url;
use failure::ResultExt;
use hmac::{Hmac, Mac, NewMac};
use itertools::Itertools;
use log::{error, warn};
use reqwest::Client;
use sha1::Sha1;

include!(concat!(env!("OUT_DIR"), "/mercury_parser_key.rs"));

type HmacSha1 = Hmac<Sha1>;

pub struct MercuryParser;

impl MercuryParser {
    pub async fn parse_url(url: &Url, client: &Client) -> Result<MercuryArticle, MercuryParserError> {
        let feedbin_url = Self::generate_url(url)?;
        let mercury_article = Self::download_mercury_article(&feedbin_url, client).await?;

        if Some(true) == mercury_article.error {
            if let Some(message) = &mercury_article.messages {
                error!("{}", message);
            }
            return Err(MercuryParserErrorKind::ServiceError)?;
        }

        if mercury_article.content.is_none() {
            return Err(MercuryParserErrorKind::NoContent)?;
        }

        // if mercury_article.next_page_url.is_some() {
        //     let mut next_page_url_option = mercury_article.next_page_url.clone();
        //     let mut content = String::new();
        //     if let Some(c) = &mercury_article.content {
        //         content.push_str(c);
        //     }

        //     while mercury_article.next_page_url.is_some() {
        //         if let Some(next_page_url_string) = &next_page_url_option {
        //             let next_page_url = Url::parse(next_page_url_string).context(MercuryParserErrorKind::IO)?;
        //             let next_page_url = Self::generate_url(&next_page_url)?;
        //             let next_page_mercury_article = Self::download_mercury_article(&next_page_url, client).await?;

        //             println!("current next: '{:?}'", next_page_url_option);
        //             println!("next next: '{:?}'", next_page_mercury_article.next_page_url);
        //             if next_page_mercury_article.next_page_url == next_page_url_option {
        //                 break;
        //             }

        //             if let Some(next_page_content) = &next_page_mercury_article.content {
        //                 content.push_str(next_page_content);
        //             }

        //             next_page_url_option = next_page_mercury_article.next_page_url.clone();
        //         }
        //     }

        //     mercury_article.content = Some(content);
        // }

        Ok(mercury_article)
    }

    async fn download_mercury_article(url: &Url, client: &Client) -> Result<MercuryArticle, MercuryParserError> {
        let mercury_article: MercuryArticle = client
            .get(url.get())
            .send()
            .await
            .context(MercuryParserErrorKind::Http)?
            .json()
            .await
            .context(MercuryParserErrorKind::Json)?;
        Ok(mercury_article)
    }

    fn generate_url(url: &Url) -> Result<Url, MercuryParserError> {
        let key_struct = MercuryParserKey::new();
        let url_string = url.to_string();
        let url_base64 = base64::encode_config(&url_string, base64::URL_SAFE);

        println!("{:?}", key_struct);

        if key_struct.mercury_user == "MERCURY_PARSER_USER_FALLBACK" {
            warn!("Build doesn't contain a API key and user for feedbins mercury parser");
            return Err(MercuryParserErrorKind::InvalidApiSecret)?;
        }

        let mut hmac_sha1 = HmacSha1::new_varkey(key_struct.mercury_key.as_bytes()).map_err(|_| MercuryParserErrorKind::HmacSha1)?;
        hmac_sha1.update(url_string.as_bytes());
        let result = hmac_sha1.finalize();
        let code_bytes = result.into_bytes();

        let code_string = code_bytes.iter().format_with("", |byte, f| f(&format_args!("{:02x}", byte))).to_string();

        let url_string = format!(
            "https://extract.feedbin.com/parser/{}/{}?base64_url={}",
            key_struct.mercury_user, code_string, url_base64
        );
        let url = Url::parse(&url_string).context(MercuryParserErrorKind::Url)?;

        Ok(url)
    }
}

#[cfg(test)]
mod tests {
    use crate::models::Url;
    use crate::util::mercury_parser::MercuryParser;
    use reqwest::Client;

    #[tokio::test(basic_scheduler)]
    async fn golem_de() {
        let url = Url::parse("https://www.golem.de/news/mate-xs-im-test-huaweis-fabelhaft-fragiler-falter-2005-148482.html").expect("Parse URL");
        let article = MercuryParser::parse_url(&url, &Client::new())
            .await
            .expect("Get article from mercury parser service");

        assert_eq!(article.author, Some("Tobias Költzsch".into()));
        assert_eq!(article.title, Some("Mate Xs im Test: Huaweis fabelhaft fragiler Falter".into()));
    }

    #[tokio::test(basic_scheduler)]
    async fn pro_linux_de() {
        let url = Url::parse("https://www.pro-linux.de/news/1/28017/openstack-ussuri-freigegeben.html").expect("Parse URL");
        let article = MercuryParser::parse_url(&url, &Client::new())
            .await
            .expect("Get article from mercury parser service");

        assert_eq!(article.author, None);
        assert_eq!(article.title, Some("OpenStack Ussuri freigegeben".into()));
    }

    #[tokio::test(basic_scheduler)]
    async fn golem_de2() {
        let url = Url::parse("https://www.golem.de/news/unterwaesche-killer-oneplus-8-pro-sieht-durch-kleidung-und-plastik-2005-148516-rss.html")
            .expect("Parse URL");
        let article = MercuryParser::parse_url(&url, &Client::new())
            .await
            .expect("Get article from mercury parser service");

        assert_eq!(article.author, Some("Tobias Költzsch".into()));
        assert_eq!(
            article.title,
            Some("Android-Smartphone: Oneplus 8 Pro sieht durch Kleidung und Plastik".into())
        );
    }
}
