use std::num::{ParseFloatError, ParseIntError};

#[derive(thiserror::Error, Debug)]
enum FinancesManagerDecodeError {
    #[error("Movement entry is not a three-part component! got {0} parts instead of 3!")]
    MovementParts(usize),
    #[error("Could not decode time!")]
    Time(ParseIntError),
    #[error("Could not decode amount!")]
    Amount(ParseFloatError),
}

#[derive(serde::Deserialize, serde::Serialize)]
pub struct FinancesManagerMovement {
    pub label: String,
    pub time: u64,
    pub amount: f32,
}

#[derive(serde::Deserialize, serde::Serialize)]
pub struct FinancesManagerAccount {
    pub name: String,
    pub movements: Vec<FinancesManagerMovement>,
}

#[derive(serde::Deserialize, serde::Serialize)]
pub struct FinancesManagerFile {
    pub accounts: Vec<FinancesManagerAccount>,
}

impl FinancesManagerFile {
    /// Parse a finance manager file
    pub fn parse(file: &str) -> anyhow::Result<Self> {
        let mut res = Self { accounts: vec![] };

        let mut curr_account = None;

        for l in file.lines() {
            // Check if we reached the end of an account
            if l.trim().is_empty() {
                if let Some(a) = curr_account {
                    res.accounts.push(a);
                }
                curr_account = None;
                continue;
            }

            if l == "==============" {
                continue;
            }

            match &mut curr_account {
                // Header
                None => {
                    curr_account = Some(FinancesManagerAccount {
                        name: l.to_string(),
                        movements: vec![],
                    });
                }

                // Account content
                Some(account) => {
                    let split = l.split(';').collect::<Vec<_>>();

                    if split.len() != 3 {
                        return Err(FinancesManagerDecodeError::MovementParts(split.len()).into());
                    }

                    account.movements.push(FinancesManagerMovement {
                        label: split[1].to_string(),
                        time: split[0]
                            .parse::<u64>()
                            .map_err(FinancesManagerDecodeError::Time)?,
                        amount: split[2]
                            .parse::<f32>()
                            .map_err(FinancesManagerDecodeError::Amount)?,
                    })
                }
            }
        }

        // Push last account

        Ok(res)
    }

    /// Encode FinancesManager file
    pub fn encode(&self) -> String {
        let mut out = String::new();

        for account in &self.accounts {
            out.push_str(&account.name);
            out.push_str("\n==============\n");
            for movement in &account.movements {
                out.push_str(&format!(
                    "{};{};{}\n",
                    movement.time,
                    movement.label.replace(';', ","),
                    movement.amount
                ));
            }
            out.push_str("\n\n\n");
        }

        out
    }
}
