use std::{
    fs::read_to_string,
    path::PathBuf,
    time::{Duration, SystemTime},
};

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

use crate::{bank::Bank, envelopes_folder, euro::Euros, target::SavingGoal, EnvelopeId};

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Transaction {
    pub time: Duration,
    pub amount: Euros,
    pub envelope: EnvelopeId,
}

impl Transaction {
    pub fn new(amount: impl Into<Euros>, id: EnvelopeId) -> Self {
        let amount = amount.into();
        let time = current_time();

        Self {
            amount,
            time,
            envelope: id,
        }
    }
}

pub fn current_time() -> Duration {
    system_time_as_unix_time(SystemTime::now())
}

pub fn system_time_as_unix_time(time: SystemTime) -> Duration {
    time.duration_since(SystemTime::UNIX_EPOCH)
        .expect("Time went backwards")
}

pub fn euros_on_track(now: Duration, start: Duration, end: Duration, end_euros: Euros) -> Euros {
    let percentage = percentage_between(now, start, end);
    let current_track: Euros = ((f32::from(end_euros)) * percentage).into();
    current_track
}

pub fn percentage_between(now: Duration, start: Duration, end: Duration) -> f32 {
    let interval = end - start;
    let time_passed = now - start;

    time_passed.as_secs_f32() / interval.as_secs_f32()
}

#[derive(Clone, Serialize, Deserialize, Default, Debug)]
pub struct Envelope {
    pub name: String,
    #[serde(default)]
    pub goal: SavingGoal,
    id: EnvelopeId,
}

impl Envelope {
    pub fn id(&self) -> EnvelopeId {
        self.id
    }

    pub fn assigned(&self, bank: &Bank) -> Euros {
        bank.assigned(self.id())
    }

    pub fn fetch_all() -> Vec<Self> {
        let mut envelopes = vec![];
        let envelope_folder = envelopes_folder();
        for entry in std::fs::read_dir(envelope_folder.as_path()).unwrap() {
            let entry = entry.unwrap();
            let path = entry.path();

            if path.is_file() && path.extension().and_then(|s| s.to_str()) == Some("toml") {
                let content = read_to_string(path).expect("Could not read the TOML file");
                let envelope: Envelope = serde_json::from_str(&content).unwrap();
                envelopes.push(envelope);
            }
        }
        envelopes
    }

    pub fn as_path(&self) -> PathBuf {
        let mut file_path = envelopes_folder().join(self.id.to_string());
        file_path.set_extension("toml");
        file_path
    }

    pub fn delete(self) {
        let path = self.as_path();
        std::fs::remove_file(path).unwrap();
    }

    pub fn persist(&self) {
        let mut file_path = envelopes_folder().join(self.id.to_string());
        file_path.set_extension("toml");

        let s = serde_json::to_string_pretty(self).unwrap();
        std::fs::write(file_path, s).unwrap();
    }

    pub fn new_empty(name: &str) -> Self {
        let name = name.to_owned();
        let id = Uuid::new_v4();
        Self {
            name,
            id,
            goal: SavingGoal::None,
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::cycle::{beginning_of_month, beginning_of_week, end_of_month};

    use super::*;

    #[test]
    fn testing() {
        let now = current_time();
        dbg!(beginning_of_month(now));
        dbg!(end_of_month(now));

        dbg!(beginning_of_week(now));
    }
}