use std::collections::HashSet;

use async_trait::async_trait;

use crate::constants::ELASTIC_PICTURES_INDEX;
use crate::harvest::harvester::Harvester;
use crate::helpers::es_helper::ElasticHelper;
use crate::models::picture::Picture;
use crate::processors::report::Report;
use crate::services::es_client::ElasticClient;
use crate::services::filesystem_reader::FileSystemReader;

#[derive(Debug, Clone)]
pub(crate) struct FileSystemHarvester {
    es_client: ElasticClient,
    es_helper: ElasticHelper,
    filesystem_reader: FileSystemReader,
    // TODO replace string path with Path or PathBuf
    path: String,
}

impl FileSystemHarvester {
    pub fn new(path: String) -> Self {
        FileSystemHarvester {
            es_client: ElasticClient::new(ELASTIC_PICTURES_INDEX),
            es_helper: ElasticHelper::new(ELASTIC_PICTURES_INDEX),
            filesystem_reader: FileSystemReader::new(),
            path,
        }
    }

    async fn clean_picture(&self, clean_report: &mut Report, errors: &mut Vec<String>, file: &Picture) {
        if self.filesystem_reader.file_exists(&file) {
            clean_report.files_processed += 1;
        } else {
            let deletion_response = self.es_client.delete_picture_record(&file).await;
            ElasticHelper::finalize_clean_report(clean_report, errors, deletion_response)
        }
    }

    async fn filter_pictures_to_update(&self, pictures: HashSet<Picture>) -> HashSet<Picture> {
        let mut pictures_to_update: HashSet<Picture> = HashSet::new();
        for picture in pictures {
            if self.es_helper.is_picture_to_update(&picture).await {
                pictures_to_update.insert(picture);
            }
        }
        pictures_to_update
    }
}

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

        let files = self.filesystem_reader.list_files_in_dir(self.path.as_ref());
        let mut pictures = self.filesystem_reader.list_pictures_in_files(files);
        harvest_report.files_to_process = pictures.len() as u32;

        pictures = self.filter_pictures_to_update(pictures).await;
        println!("number of pictures to update: {}", pictures.len());
        self.es_client.index_pictures(&pictures, &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<Picture> = HashSet::new();

        let files_result = self.es_helper.find_pictures(&self.path).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;

        for file in files_to_check {
            self.clean_picture(&mut clean_report, &mut errors, &file).await
        }

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

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