use chrono::{DateTime, Duration, Local, Utc};
use parse_duration::parse;
use reqwest;
use reqwest::blocking::Client;

use select::document::Document;
use select::predicate::Name;
use std::collections::HashMap;
// extern crate url;
use url::form_urlencoded::{byte_serialize};
use log::*;


#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct Auction {
    pub price: f32,
    pub desc: String,
    pub gcal: String,
    pub thumb: String,
    pub remaining: i64,
    pub url: String,
    pub timestamp: i64,
    pub is_price_final: bool,
}


impl Auction {
    fn update(&mut self, client: &Client) {




        // println!("now: {:?} auction: {:?}", Utc::now().with_timezone(&Local).timestamp(), self.timestamp);
        self.is_price_final = Utc::now().with_timezone(&Local).timestamp() > self.timestamp;
        // self.price = price_from_auction(&self.url)
        // TODO: delete ^^ and enable lines below
        if cfg!(debug_assertions) {
            dbg!(&self.price);
        } 

        *self = auction_details(&self, client);

        // if was_not_final != self.is_price_final {
        //     self.price = auction_details(&self.url);
        //     if cfg!(debug_assertions) {
        //         dbg!(&self.price);
        //     } 

        // }
    }
}

#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct Query {
    #[serde(default)]
    pub auctions: HashMap<String, Auction>,
    #[serde(skip_deserializing)]
    avg_price: f32,
}

impl Query {
    fn get_avg_price(&mut self) -> f32 {
        self.auctions.iter().map(|(_u, a)| a.price).sum::<f32>() / self.auctions.len() as f32
    }
    



    fn detect_expired(&self, client: &Client) -> HashMap<String, Auction> {
        self.auctions
            .iter()
            .map(|(u, a)| {
                let mut a1 = a.clone();
                a1.update(client);
                (u.clone(), a1)
            })
            .collect()
    }
}

#[derive(Serialize, Deserialize, Debug, Default, Clone)]
pub struct Account {
    // SEARCH URL: {AUCTION_URL: AUCTION}
    // pub queries: HashMap<String, HashMap<String, Auction>>,
    pub queries: HashMap<String, Query>,
}

impl Account {
    pub fn run_queries(&self, client: &Client) -> HashMap<String, Query> {
        self.queries
            .clone()
            .into_iter()
            .map(|(url, mut query)| {
                (url.clone(), {
                    query.auctions.extend(auctions_from_url(&url, client));
                    query.avg_price = query.get_avg_price();
                    if cfg!(debug_assertions) {
                        // dbg!(&query);
                    } 
                    query
                })
            })
            .map(|(url, mut query)| {
                query.auctions = query.detect_expired(client);
                (url, query)
            })
            .collect()
    }

    pub fn updated(&self, client: &Client) -> Account {
        Account {
            queries: self.run_queries(client), // .iter()
                                         // .map(|(k,v)| (k.clone(), Query{auctions: v.clone(), .. Default::default()}))
                                         // .collect()
        }
    }
}

fn date_to_gcal(date: DateTime<Local>) -> String {
    format!("{}", date.format("%Y%m%dT%H%M00/%Y%m%dT%H%M00"))
}

/// Parse a relative offset "x Tage HH:MM" into proper time
fn parse_remaining(timestring: &String) -> Option<Duration> {
    match parse(&format!(
        "{} minutes",
        timestring.replace(":", " hours ").replace("Tage", "days")
    ))
    .ok()
    {
        Some(old_duration) => Duration::from_std(old_duration).ok(),
        None => None,
    }
}

fn parse_price(price: &str) -> Option<f32> {
    price
        .to_string()
        .replace("\n", "")
        .replace("\t", "")
        .replace(".", "")
        .replace(",", ".")
        .replace(" EUR", "")
        .parse()
        .ok()
}


fn auction_details(auction: &Auction, client: &Client) -> Auction {
    let mut updated_auction = auction.clone();

    if auction.is_price_final {
        return updated_auction;
    }

    // let client = reqwest::blocking::Client::builder()
    //     .max_idle_per_host(8)
    //     // .timeout(Some(core::time::Duration::from_secs(10)))
    //     .build()
    //     .unwrap();


    match client.get(&auction.url).send() {
        Ok(resp) => {
            // if !resp.status().is_success() {
            //     println!("ERR {:?}", &resp.text());
            // }

            let text = resp.text().unwrap_or("".to_string());

            for node in Document::from_read(text.as_bytes())
                .unwrap()
                .find(Name("b"))
                .flat_map(|x| x.children())
                .map(|x| x.text())
                .filter(|x| x.contains("EUR"))
            {
                if let Some(p) = parse_price(&node) {
                    updated_auction.price = p;
                }
            }

            for node in Document::from_read(text.as_bytes())
                .unwrap()
                .find(Name("img"))
                .filter(|x| x.attr("id") == Some("item_top_img"))
                .map(|x| x.attr("src").unwrap_or("http://egun.de/market/images/picture.gif"))
                // .filter(|x| x)
            {
                // dbg!(&node);
                updated_auction.thumb = abs_image_url(node);
            }
        
        
        }


        Err(e) => {
            error!("Could not fetch auction details: {:?}", e);
            // println!("Setting this price to final");
            // updated_auction.is_price_final = true;

        },
    }

    updated_auction

}

fn abs_image_url(rel_url: &str) -> String {
    format!("http://egun.de/market/{}", rel_url)
}


fn auctions_from_url(url: &str, client: &Client) -> HashMap<String, Auction> {
    let mut auctions = HashMap::new();
    // dbg!(&url);

 

    match client.get(url).send() {
        Ok(resp) => {
            if !resp.status().is_success() {
                // println!("ERR {:?}", resp.text_with_charset("windows-1252"));
                
                return auctions;
            }

            // dbg!(&resp.text_with_charset("windows-1252"));
            // dbg!(&resp.text());
            let text = resp.text().unwrap_or("".to_string());
            // let text = resp.text_with_charset("windows-1252").unwrap_or("".to_string());

            for node in Document::from_read(text.as_bytes())
                .unwrap()
                // .find(Name("a"))
                // .filter(|n| n.attr("href").is_some())
                // .filter(|n| n.attr("href").unwrap().contains("item.php?id="))
                .find(Name("tr"))
                .filter(|x| x.attr("bgcolor").is_some())
                .filter(|x| x.attr("align").is_none())
            {
                // Get auction name
                if let Some(name) = node
                    .children()
                    .into_iter()
                    .filter(|x| x.name() == Some("td"))
                    .filter(|x| x.attr("align") == Some("LEFT"))
                    .flat_map(|x| x.children())
                    .filter(|x| x.name() == Some("a"))
                    .flat_map(|x| x.children())
                    .map(|x| x.text())
                    // .filter(|x| x.name() == Some("Text"))
                    .collect::<Vec<_>>()
                    .get(0)
                {
                    // If we have the name, go on finding other details
                    // instantiate mutable Auction
                    let mut auction = Auction::default();
                    auction.desc = name.clone();
                    auction.thumb = format!("http://egun.de/market/images/picture.gif");

                    // get image
                    if let Some(img) = node
                        .children()
                        .into_iter()
                        .filter(|x| x.name() == Some("td"))
                        .filter(|x| x.attr("align") == Some("center"))
                        .flat_map(|x| x.descendants())
                        .filter(|x| x.name() == Some("img"))
                        .filter(|x| match x.attr("src") {
                            Some(src) => src.contains("cache"),
                            None => false,
                        })
                        .map(|x| x.attr("src").unwrap()) // we just tested
                        .collect::<Vec<_>>()
                        .get(0)
                    {
                        auction.thumb = abs_image_url(img);
                    }

                    // get price
                    if let Some(price) = node
                        .children()
                        .filter(|x| x.text().contains("EUR"))
                        .flat_map(|x| x.children())
                        .map(|x| parse_price(&x.text()))
                        .flat_map(|x| x)
                        .collect::<Vec<_>>()
                        .get(0)
                    {
                        auction.price = price.clone();
                    }

                    // get article url
                    if let Some(url) = node
                        .children()
                        .into_iter()
                        .filter(|x| x.name() == Some("td"))
                        .filter(|x| x.attr("align") == Some("LEFT"))
                        .flat_map(|x| x.children())
                        .filter(|x| x.name() == Some("a"))
                        .map(|x| x.attr("href"))
                        .filter_map(|x| x)
                        .collect::<Vec<_>>()
                        .get(0)
                    {
                        auction.url = format!("http://egun.de/market/{}", url);
                        // price_from_auction(&auction.url);
                    }
                    // TODO: check if https://doc.rust-lang.org/std/time/struct.SystemTime.html works too
                    if let Some(remaining) = parse_remaining(
                        &node
                            .children()
                            .filter(|x| x.attr("align") == Some("center"))
                            .filter(|x| x.attr("nowrap").is_some())
                            .flat_map(|x| x.children())
                            .filter(|x| !x.text().is_empty())
                            .map(|x| x.text())
                            .collect::<Vec<_>>()
                            .join(" "),
                    ) {
                        // dbg!(&t_remaining.children());

                        let end_date = Utc::now().with_timezone(&Local) + remaining;
                        let urlencoded: String = byte_serialize(auction.desc.as_bytes()).collect();
                        auction.gcal = format!("http://www.google.com/calendar/event?action=TEMPLATE&dates={}&text={}&location=&details=", date_to_gcal(end_date), urlencoded);
                        auction.remaining = remaining.num_seconds();
                        auction.timestamp = end_date.timestamp();
                        // println!("ENDS\t{:?}", date_to_gcal(remaining));
                    }

                    auctions.insert(auction.url.clone(), auction);
                }
            }
        }
        Err(e) => error!("Retrieve auction list {:?}", e),
    }

    // if let Ok(mut resp) = client.get(url).send() {

    auctions
}
