use crate::models::Url;
use failure::{format_err, Error};
use libxml::{parser::Parser, xpath::Context};
use log::warn;
use reqwest::header::CONTENT_TYPE;
use reqwest::Client;
use std::str;
use std::str::FromStr;

pub struct IconScraper {
    potential_urls: Vec<Url>,
}

impl IconScraper {
    pub async fn from_http(url: &Url, client: &Client) -> Result<Self, Error> {
        let parser = Parser::default_html();
        let result = client.get(url.get()).send().await.map_err(|e| {
            warn!("Failed to download url: {} - {}", url, e);
            e
        })?;
        let html = result.text().await.map_err(|e| {
            warn!("Failed to download url: {} - {}", url, e);
            e
        })?;

        if let Ok(doc) = parser.parse_string(html) {
            if let Ok(xpath_ctx) = Context::new(&doc) {
                let mut res = Vec::new();
                res.append(&mut Self::xpath(&xpath_ctx, url, "//link[@rel='apple-touch-icon']"));
                res.append(&mut Self::xpath(&xpath_ctx, url, "//link[@rel='icon']"));
                res.append(&mut Self::xpath(&xpath_ctx, url, "//link[@rel='shortcut icon']"));
                res.sort_by(|(a, _), (b, _)| b.cmp(a));
                let mut scraper = IconScraper {
                    potential_urls: res.iter().map(|(_size, url)| url.clone()).collect(),
                };

                // don't forget about goold old /favicon.ico
                let mut base_url = url.get();
                base_url.set_path("");
                base_url.set_query(None);
                if let Ok(basic_favicon_url) = base_url.join("favicon.ico") {
                    scraper.potential_urls.push(Url::new(basic_favicon_url));
                }

                return Ok(scraper);
            }
        } else {
            warn!("Failed to parse HTML");
        }

        Err(format_err!("some err"))
    }

    pub async fn fetch_best(&self, client: &Client) -> Option<(Option<String>, Url, Vec<u8>)> {
        for url in &self.potential_urls {
            if let Ok(response) = client.get(url.get()).send().await {
                let mime = match response.headers().get(CONTENT_TYPE) {
                    Some(content_type) => match content_type.to_str().ok() {
                        Some(content_type) => Some(content_type.to_owned()),
                        None => None,
                    },
                    None => None,
                };
                if let Ok(response_buffer) = response.bytes().await {
                    return Some((mime, url.clone(), response_buffer.to_vec()));
                }
            }
        }
        None
    }

    fn xpath(xpath_ctx: &Context, base: &Url, xpath: &str) -> Vec<(Option<i32>, Url)> {
        let mut res = Vec::new();
        if let Ok(xpath_result) = xpath_ctx.evaluate(xpath) {
            let xpath_result = xpath_result.get_nodes_as_vec();
            for node in xpath_result {
                if let Some(url) = node.get_property("href") {
                    if let Ok(url) = base.get().join(&url) {
                        let size = match node.get_property("sizes") {
                            None => None,
                            Some(size_str) => {
                                let width_height: Vec<&str> = size_str.split('x').collect();
                                let mut size: Option<i32> = None;
                                if width_height.len() == 2 {
                                    if let Some(width) = width_height.get(0) {
                                        if let Some(height) = width_height.get(1) {
                                            if let Ok(width) = i32::from_str(width) {
                                                if let Ok(height) = i32::from_str(height) {
                                                    size = Some(width * height);
                                                }
                                            }
                                        }
                                    }
                                    size
                                } else {
                                    None
                                }
                            }
                        };
                        res.push((size, Url::new(url)));
                    } else {
                        warn!("unable to parse url '{}'", url);
                    }
                } else {
                    warn!("<link> tag is missin href property");
                }
            }
        }

        res
    }
}
