use std::{collections::BTreeMap, time::Duration};

use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::{
    bank_path,
    envelope::{current_time, Transaction},
    euro::Euros,
    EnvelopeId,
};

pub type BankId = Uuid;

#[derive(Default, Serialize, Deserialize)]
struct BankDeposit {
    amount: Euros,
    time: Duration,
}

impl BankDeposit {
    fn new(amount: Euros) -> Self {
        let time = current_time();
        Self { amount, time }
    }
}

#[derive(Default, Serialize, Deserialize, Clone, Copy)]
struct TimedEuros {
    amount: Euros,
    time: Duration,
}

impl TimedEuros {
    pub fn new_current(amount: Euros) -> Self {
        let time = current_time();
        Self { amount, time }
    }

    pub fn sum_up(vars: &[Self]) -> Euros {
        vars.iter().fold(Euros::default(), |sum, t| sum + t.amount)
    }

    pub fn sum_between(vars: &[Self], start: Duration, end: Duration) -> Euros {
        vars.iter().fold(Euros::default(), |sum, t| {
            sum + if t.time > start && t.time < end {
                t.amount
            } else {
                Euros::default()
            }
        })
    }

    pub fn _sum_until(vars: &[Self], end: Duration) -> Euros {
        Self::sum_between(vars, Duration::ZERO, end)
    }
    pub fn _sum_from(vars: &[Self], start: Duration) -> Euros {
        Self::sum_between(vars, start, Duration::MAX)
    }
}

#[derive(Default, Serialize, Deserialize)]
pub struct Bank {
    withdrawals: Vec<Transaction>,
    deposits: Vec<BankDeposit>,
    assigned: BTreeMap<EnvelopeId, Vec<TimedEuros>>,
}

impl Bank {
    pub fn load() -> Self {
        let path = bank_path();
        if path.exists() {
            let s = std::fs::read_to_string(path.as_path()).unwrap();
            serde_json::from_str(s.as_str()).unwrap()
        } else {
            let bank = Self::default();
            bank.persist();
            bank
        }
    }

    pub fn assigned(&self, envelope: EnvelopeId) -> Euros {
        let vec = self.assigned.get(&envelope).cloned().unwrap_or_default();
        TimedEuros::sum_up(&vec)
    }

    pub fn assigned_in_timeframe(
        &self,
        envelope: EnvelopeId,
        start: Duration,
        end: Duration,
    ) -> Euros {
        let vec = self.assigned.get(&envelope).cloned().unwrap_or_default();
        TimedEuros::sum_between(&vec, start, end)
    }

    pub fn money_spent_in_timeframe(
        &self,
        envelope: EnvelopeId,
        start: Duration,
        end: Duration,
    ) -> Euros {
        self.withdrawals.iter().fold(Euros::default(), |sum, t| {
            sum + if t.time > start && t.time < end && t.envelope == envelope {
                t.amount
            } else {
                Euros::default()
            }
        })
    }

    pub fn clear_assign(&mut self, envelope: EnvelopeId) {
        self.assigned.remove(&envelope);
        self.persist();
    }

    fn persist(&self) {
        let path = bank_path();
        let s = serde_json::to_string_pretty(self).unwrap();

        std::fs::write(path, s).unwrap();
    }

    pub fn new_transaction(&mut self, transaction: Transaction) {
        self.remove_assign(transaction.envelope, transaction.amount);
        self.withdrawals.push(transaction);
        self.persist();
    }

    pub fn remove_money(&mut self, amount: Euros) {
        assert!(amount.euros() > 0);
        let amount = amount.as_negative();
        self.deposits.push(BankDeposit::new(amount));
        self.persist();
    }

    pub fn insert_money(&mut self, amount: Euros) {
        assert!(amount.euros() > 0);
        self.deposits.push(BankDeposit::new(amount));
        self.persist();
    }

    pub fn remove_assign(&mut self, envelope: EnvelopeId, amount: Euros) {
        let amount = amount.as_negative();
        self.assign_money(envelope, amount);
    }

    pub fn assign_money(&mut self, envelope: EnvelopeId, amount: Euros) {
        let amount = TimedEuros::new_current(amount);
        self.assigned
            .entry(envelope)
            .and_modify(|e| e.push(amount))
            .or_insert(vec![amount]);
        self.persist();
    }

    pub fn total_money(&self) -> Euros {
        self.inputs_euros() - self.transactions_euros()
    }

    pub fn unassigned(&self) -> Euros {
        self.total_money() - self.assigned_euros()
    }

    fn assigned_euros(&self) -> Euros {
        self.assigned
            .iter()
            .fold(Euros::default(), |sum, t| sum + TimedEuros::sum_up(t.1))
    }

    fn inputs_euros(&self) -> Euros {
        self.deposits
            .iter()
            .fold(Euros::default(), |sum, t| sum + t.amount)
    }

    fn transactions_euros(&self) -> Euros {
        self.withdrawals
            .iter()
            .fold(Euros::default(), |sum, t| sum + t.amount)
    }
}