use std::{
    collections::{HashMap, HashSet},
    iter::FromIterator,
    rc::Rc,
};

use futures::join;
use futures_signals::signal::Mutable;
use shared::{
    api::{
        endpoints::{category, meta},
        ApiEndpoint,
    },
    domain::{
        category::{Category, CategoryId, CategoryResponse, CategoryTreeScope, GetCategoryRequest},
        jig::JigSearchQuery,
        meta::{Affiliation, AffiliationId, AgeRange, AgeRangeId, Goal, GoalId, MetadataResponse},
        user::UserProfile,
    },
    error::EmptyError,
};
use utils::{
    languages::{Language, JIG_LANGUAGES},
    prelude::*,
};

#[derive(Debug)]
pub struct SearchSelected {
    pub goals: Mutable<HashSet<GoalId>>,
    pub affiliations: Mutable<HashSet<AffiliationId>>,
    pub categories: Mutable<HashSet<CategoryId>>,
    pub age_ranges: Mutable<HashSet<AgeRangeId>>,
    pub language: Mutable<Option<String>>,
    pub query: Mutable<String>,
}

impl SearchSelected {
    pub fn new() -> Self {
        Self {
            goals: Mutable::new(HashSet::new()),
            affiliations: Mutable::new(HashSet::new()),
            categories: Mutable::new(HashSet::new()),
            age_ranges: Mutable::new(HashSet::new()),
            language: Mutable::new(None),
            query: Mutable::new(String::new()),
        }
    }

    pub fn set_from_profile(&self, profile: &UserProfile) {
        let mut affiliations = self.affiliations.lock_mut();
        if profile.affiliations.len() > 0 {
            affiliations.clear();
            affiliations.extend(profile.affiliations.clone());
        }

        let mut state_age_ranges = self.age_ranges.lock_mut();
        if profile.age_ranges.len() > 0 && state_age_ranges.len() == 0 {
            state_age_ranges.clear();
            state_age_ranges.extend(profile.age_ranges.clone());
        }
    }

    pub fn from_search_request(search: JigSearchQuery) -> Self {
        Self {
            goals: Mutable::new(HashSet::from_iter(search.goals)),
            affiliations: Mutable::new(HashSet::from_iter(search.affiliations)),
            categories: Mutable::new(HashSet::from_iter(search.categories)),
            age_ranges: Mutable::new(HashSet::from_iter(search.age_ranges)),
            language: Mutable::new(None),
            query: Mutable::new(search.q),
        }
    }

    pub fn to_search_request(&self) -> JigSearchQuery {
        log::info!("{:?}", self);
        JigSearchQuery {
            q: self.query.lock_ref().to_owned(),
            age_ranges: self.age_ranges.lock_ref().to_owned().into_iter().collect(),
            affiliations: self
                .affiliations
                .lock_ref()
                .to_owned()
                .into_iter()
                .collect(),
            categories: self.categories.lock_ref().to_owned().into_iter().collect(),
            goals: self.goals.lock_ref().to_owned().into_iter().collect(),
            page: Some(0),
            language: self.language.get_cloned(),
            ..Default::default()
        }
    }
}

pub struct SearchOptions {
    pub age_ranges: Mutable<Vec<AgeRange>>,
    pub goals: Mutable<Vec<Goal>>,
    pub affiliations: Mutable<Vec<Affiliation>>,
    pub categories: Mutable<Vec<Category>>,
    pub category_label_lookup: Mutable<HashMap<CategoryId, String>>,
    pub languages: Rc<Vec<Language>>,
}

impl SearchOptions {
    pub fn new() -> Self {
        Self {
            age_ranges: Mutable::new(vec![]),
            goals: Mutable::new(vec![]),
            affiliations: Mutable::new(vec![]),
            categories: Mutable::new(vec![]),
            category_label_lookup: Mutable::new(HashMap::new()),
            languages: Rc::new(JIG_LANGUAGES.clone()),
        }
    }

    pub async fn populate_options(&self) {
        let _ = join!(self.load_metadata(), self.load_categories());
    }

    async fn load_metadata(&self) -> Result<(), EmptyError> {
        match api_no_auth::<MetadataResponse, EmptyError, ()>(
            meta::Get::PATH,
            meta::Get::METHOD,
            None,
        )
        .await
        {
            Err(e) => Err(e),
            Ok(res) => {
                // only set values if they're not set yet from the profile
                if self.affiliations.lock_ref().is_empty() {
                    self.affiliations.set(res.affiliations);
                }
                if self.age_ranges.lock_ref().is_empty() {
                    self.age_ranges.set(res.age_ranges);
                }
                if self.goals.lock_ref().is_empty() {
                    self.goals.set(res.goals);
                }
                Ok(())
            }
        }
    }

    async fn load_categories(&self) -> Result<(), EmptyError> {
        let req = GetCategoryRequest {
            ids: Vec::new(),
            scope: Some(CategoryTreeScope::Decendants),
        };

        match api_no_auth::<CategoryResponse, EmptyError, GetCategoryRequest>(
            category::Get::PATH,
            category::Get::METHOD,
            Some(req),
        )
        .await
        {
            Err(e) => Err(e),
            Ok(res) => {
                let mut category_label_lookup = HashMap::new();
                Self::get_categories_labels(&res.categories, &mut category_label_lookup);
                self.category_label_lookup.set(category_label_lookup);
                if self.categories.lock_ref().is_empty() {
                    self.categories.set(res.categories);
                }
                Ok(())
            }
        }
    }

    fn get_categories_labels(categories: &Vec<Category>, lookup: &mut HashMap<CategoryId, String>) {
        for category in categories {
            lookup.insert(category.id.clone(), category.name.clone());
            Self::get_categories_labels(&category.children, lookup);
        }
    }
}
