use std::collections::HashSet;

use async_trait::async_trait;

use crate::constants::ELASTIC_FLICKR_PHOTOS_INDEX;
use crate::harvest::harvester::Harvester;
use crate::helpers::es_helper::ElasticHelper;
use crate::models::flickr_photo::FlickrPhoto;
use crate::processors::report::Report;
use crate::services::es_client::ElasticClient;
use crate::services::flickr_client::FlickrClient;

#[derive(Debug, Clone)]
pub(crate) struct FlickrHarvester {
    es_client: ElasticClient,
    es_helper: ElasticHelper,
    flickr_client: FlickrClient,
}

impl FlickrHarvester {
    pub fn new() -> Self {
        FlickrHarvester {
            es_client: ElasticClient::new(ELASTIC_FLICKR_PHOTOS_INDEX),
            es_helper: ElasticHelper::new(ELASTIC_FLICKR_PHOTOS_INDEX),
            flickr_client: FlickrClient::new(),
        }
    }

    async fn clean_flickr_photo(&self, clean_report: &mut Report, errors: &mut Vec<String>, photo: &FlickrPhoto, photos_from_flickr: &HashSet<FlickrPhoto>) {
        if photos_from_flickr.contains(photo) {
            clean_report.files_processed += 1;
        } else {
            let deletion_response = self.es_client.delete_flickr_photo_record(&photo).await;
            ElasticHelper::finalize_clean_report(clean_report, errors, deletion_response)
        }
    }

    async fn filter_flickr_photo_to_update(&self, photos: HashSet<FlickrPhoto>) -> HashSet<FlickrPhoto> {
        let mut photos_to_update: HashSet<FlickrPhoto> = HashSet::new();
        for photo in photos {
            if self.es_helper.is_flickr_photo_to_update(&photo).await {
               photos_to_update.insert(photo);
            }
        }
        photos_to_update
    }
}

#[async_trait]
impl Harvester for FlickrHarvester {
    async fn update_db(&self) -> Result<Report, Vec<String>> {
        let mut harvest_report: Report = Report::new();
        let mut errors: Vec<String> = Vec::new();

        let mut photos = self.flickr_client.list_flickr_photos().await;
        harvest_report.files_to_process = photos.len() as u32;

        photos = self.filter_flickr_photo_to_update(photos).await;
        println!("number of photos to update: {}", photos.len());
        self.es_client.index_flickr_photos(&photos, &mut harvest_report, &mut errors).await;

        if harvest_report.errors > 0 {
            Err(errors)
        } else {
            Ok(harvest_report)
        }
    }

    async fn clean_db(&self) -> Result<Report, Vec<String>> {
        let mut clean_report: Report = Report::new();
        let mut errors: Vec<String> = Vec::new();
        let mut files_to_check: HashSet<FlickrPhoto> = HashSet::new();

        let files_result = self.es_helper.get_all_flickr_photos().await;
        if files_result.is_ok() {
            files_to_check = files_result.unwrap()
        } else {
            errors.extend_from_slice(&files_result.err().unwrap())
        }
        clean_report.files_to_process = files_to_check.len() as u32;

        let photos_from_flickr = self.flickr_client.list_flickr_photos().await;
        for file in files_to_check {
            self.clean_flickr_photo(&mut clean_report, &mut errors, &file, &photos_from_flickr).await
        }

        if clean_report.errors > 0 {
            Err(errors)
        } else {
            Ok(clean_report)
        }
    }

    fn get_es_client(&self) -> &ElasticClient {
        &self.es_client
    }
}
