use crate::controllers::HttpResult;
use crate::extractors::auth_extractor::AuthExtractor;
use crate::models::accounts::Account;
use crate::services::{accounts_service, files_service, movements_service};
use crate::utils::time_utils::time;
use actix_web::{HttpResponse, web};
use std::collections::HashMap;

#[derive(serde::Serialize)]
struct GlobalStats {
    global_balance: f32,
    number_movements: usize,
    number_files: usize,
    total_files_size: u64,
}

/// Get global statistics
pub async fn global(auth: AuthExtractor) -> HttpResult {
    let movements = movements_service::get_all_movements_user(auth.user.id()).await?;
    let files = files_service::get_all_files_user(auth.user.id()).await?;

    Ok(HttpResponse::Ok().json(GlobalStats {
        global_balance: movements.iter().fold(0.0, |sum, m| sum + m.amount),
        number_movements: movements.len(),
        number_files: files.len(),
        total_files_size: files.iter().fold(0, |sum, m| sum + m.file_size as u64),
    }))
}

#[derive(serde::Deserialize)]
pub struct BalanceVariationQuery {
    #[serde(skip_serializing_if = "Option::is_none")]
    interval: Option<u64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    start: Option<i64>,
    #[serde(skip_serializing_if = "Option::is_none")]
    end: Option<i64>,
}

/// Statistic dataset entry
#[derive(serde::Serialize, Debug, Clone)]
struct StatEntry {
    time: i64,
    /// Account balances. Note: due to JSON limitation, we had to turn account id into strings
    #[serde(flatten)]
    balances: HashMap<String, f32>,
}

impl StatEntry {
    fn init_first(time: i64, accounts: &[Account]) -> Self {
        Self {
            time,
            balances: accounts
                .iter()
                .map(|a| (a.id().0.to_string(), 0.0))
                .collect(),
        }
    }
}

/// Accounts balance variation
pub async fn balance_variation(
    auth: AuthExtractor,
    query: web::Query<BalanceVariationQuery>,
) -> HttpResult {
    let start = query.start.unwrap_or((time() - 3600 * 24 * 30) as i64);
    let end = query.end.unwrap_or(time() as i64);
    let interval = query.interval.unwrap_or(3600 * 24);

    let accounts = accounts_service::get_list_user(auth.user.id()).await?;
    let mut movements = movements_service::get_all_movements_user(auth.user.id()).await?;
    movements.sort_by(|a, b| a.time.cmp(&b.time));

    let mut dataset = vec![];
    let mut stat_entry = StatEntry::init_first(start, &accounts);

    // Iter movements
    for m in movements {
        if m.time > end {
            break;
        }

        // Check if it is time to go the the next entry
        while m.time > stat_entry.time {
            dataset.push(stat_entry.clone());
            stat_entry.time += interval as i64;
        }

        let target_account_id = m.account_id().0.to_string();
        let old_amount = *stat_entry.balances.get(&target_account_id).unwrap_or(&0.0);
        stat_entry
            .balances
            .insert(target_account_id, old_amount + m.amount);
    }

    // Add missing end entries, if necessary
    while stat_entry.time < end {
        dataset.push(stat_entry.clone());
        stat_entry.time += interval as i64;
    }

    // Final push
    dataset.push(stat_entry);

    Ok(HttpResponse::Ok().json(dataset))
}
