extern crate confy;
extern crate oauth;
extern crate reqwest;

use std::collections::HashSet;
use std::time::{SystemTime, UNIX_EPOCH};

use chrono::{DateTime, NaiveDateTime, Utc};
use futures::executor::block_on;
use oauth::{Builder, Credentials, Request};
use reqwest::{Client, Error, Response};
use serde::{Deserialize, Serialize};
use serde_json::Value;

use crate::config::Config;
use crate::constants::*;
use crate::models::flickr_photo::FlickrPhoto;

#[derive(Debug, Clone)]
pub struct FlickrClient {
    consumer: Credentials,
    token: Credentials,
    config: Config,
}

#[derive(oauth::Authorize, Debug, Clone)]
struct FlickrGetPhotosParams<'a> {
    method: &'a str,
    user_id: &'a str,
    format: &'a str,
    extras: &'a str,
    page: u8,
}

#[derive(oauth::Authorize, Debug, Clone)]
struct FlickrGetInfoParams<'a> {
    method: &'a str,
    api_key: &'a str,
    photo_id: &'a str,
    secret: &'a str,
    format: &'a str,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
struct FlickrResponse {
    page: u8,
    pages: u8,
    perpage: u8,
    #[serde(skip)]
    total: String,
    photo: HashSet<FlickrPhoto>,
}

impl<'a> FlickrClient {
    pub fn new() -> Self {
        let config: Config = confy::load("photobook").unwrap_or_default();
        FlickrClient {
            consumer: Credentials::new(config.flickr_key.clone(), config.flickr_secret.clone()),
            token: Credentials::new(config.flickr_oauth_token.clone(), config.flickr_oauth_secret.clone()),
            config,
        }
    }

    pub async fn get_flickr_photo_taken_date(&self, photo: &FlickrPhoto) -> Option<DateTime<Utc>> {
        let params = FlickrGetInfoParams {
            method: "flickr.photos.getInfo",
            api_key: self.config.flickr_key.as_str(),
            photo_id: photo.id.as_str(),
            secret: photo.secret.as_str(),
            format: "json"
        };

        let response = self.send_simple_request(params).await.expect("flickr get info error");
        let mut response_body: String = block_on(response.text()).unwrap_or_default();
        response_body = response_body.replace("jsonFlickrApi(", "").replace(")", "");
        let result: Value = serde_json::from_str(response_body.as_str()).unwrap_or_default();
        let taken_date = result
            .get("photo").expect("no photo object in flickr response")
            .get("dates").expect("no dates object in flickr response")
            .get("taken");

        if taken_date.is_none() {
            None
        } else {
            let date: String = serde_json::from_value(taken_date.unwrap().clone()).unwrap_or_default();
            let date_time = NaiveDateTime::parse_from_str(date.as_str(), "%Y-%m-%d %H:%M:%S");
            Some(DateTime::from_utc(date_time.unwrap(),Utc))
        }
    }

    pub async fn list_flickr_photos(&self) -> HashSet<FlickrPhoto> {
        let mut photos: HashSet<FlickrPhoto> = HashSet::new();
        let mut errors: Vec<String> = Vec::new();
        let mut scroll = true;
        let mut params = FlickrGetPhotosParams {
            method: "flickr.people.getPhotos",
            user_id: "89897882@N05",
            format: "json",
            extras: "original_format",
            page: 0,
        };

        while scroll {
            params.page += 1;
            scroll = self.scroll_flickr_photos(&mut photos, &mut errors, params.clone()).await;
        }
        photos
    }

    async fn scroll_flickr_photos(&self, photos: &mut HashSet<FlickrPhoto>, errors: &mut Vec<String>, params: FlickrGetPhotosParams<'_>) -> bool {
        let mut scroll = false;
        let response = self.send_request(params.clone()).await;
        if response.is_ok() {
            let mut response_body: String = block_on(response.unwrap().text()).unwrap_or_default();
            response_body = response_body.replace("jsonFlickrApi(", "").replace(")", "");
            let result: Value = serde_json::from_str(response_body.as_str()).unwrap_or_default();
            let flickr_response: FlickrResponse = serde_json::from_value(result.get("photos").unwrap().clone()).unwrap_or_default();
            let mut flickr_photos = flickr_response.photo;
            if flickr_response.pages != flickr_response.page {
                scroll = true;
            }
            for flickr_photo in flickr_photos.drain() {
                photos.insert(flickr_photo);
            }
            println!("number of photos scrolled from Flickr: {}", photos.len());
        } else {
            errors.push(response.err().unwrap().to_string())
        }
        scroll
    }

    async fn send_request(&self, params: FlickrGetPhotosParams<'_>) -> Result<Response, Error> {
        let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).unwrap_or_default().as_secs() + 3600;
        let mut builder = Builder::new(self.consumer.clone(), oauth::HmacSha1);
        builder.token(self.token.clone())
            .nonce(FLICKR_NONCE)
            .timestamp(timestamp);
        let request: Request = builder.get(FLICKR_API_URL, params);
        let parsed_authorization: Vec<&str> = request.authorization.rsplit("=\\\"").collect();
        let mut signature = String::from(parsed_authorization[0]);
        signature.truncate(32);

        let uri = request.data +
            "&oauth_consumer_key=" + self.config.flickr_key.as_str() +
            "&oauth_nonce=" + FLICKR_NONCE +
            "&oauth_signature_method=HMAC-SHA1" +
            "&oauth_timestamp=" + timestamp.to_string().as_str() +
            "&oauth_token=" + self.config.flickr_oauth_token.as_str() +
            "&oauth_signature=" + signature.as_str();

        let client = Client::new();
        client.get(&uri).send().await
    }

    async fn send_simple_request(&self, params: FlickrGetInfoParams<'_>) -> Result<Response, Error> {
        let builder = Builder::new(self.consumer.clone(), oauth::HmacSha1);
        let request: Request = builder.get(FLICKR_API_URL, params);

        let client = Client::new();
        client.get(&request.data).send().await
    }
}
