use self::transaction_structure::Transaction;
use crate::API;
use read_input::prelude::*;
use reqwest::Client;
use round::round;

mod budget_structure;
mod category_structure;
mod payee_structure;
mod transaction_structure;

pub struct YnabClient {
    client: Client,
    apikey: String,
}

impl YnabClient {
    pub fn new(apikey: String) -> Self {
        let client = reqwest::Client::new();
        YnabClient { client, apikey }
    }
}

#[derive(Clone)]
pub struct YnabMoney {
    // YNAB Stores in millivalue because reasons
    amount: i64,
}

impl std::fmt::Display for YnabMoney {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        write!(
            f,
            "${:.2}",
            convert_milli_units_to_currency_amount(self.amount)
        )
    }
}

#[derive(Clone)]
pub struct YnabTransaction {
    pub transaction: Transaction,
    pub amount: YnabMoney,
}

fn convert_milli_units_to_currency_amount(amount: i64) -> f64 {
    let number_to_round_to: f64 = 1.0 / 10.0;
    let rounded = round((amount as f64) * number_to_round_to, 2) / number_to_round_to;

    rounded * (0.1 / 100.00)
}

pub async fn list_budgets(
    client: &YnabClient,
) -> Result<Vec<budget_structure::Budget>, Box<dyn std::error::Error>> {
    let response: budget_structure::Response = client
        .client
        .get(format!("{0}{1}", API, "budgets?include_accounts=true"))
        .bearer_auth(&client.apikey)
        .send()
        .await?
        .json()
        .await?;

    Ok(response.data.budgets)
}

pub fn choose_budget(
    budgets: &[budget_structure::Budget],
    mut writer: impl std::io::Write,
) -> Result<&String, Box<dyn std::error::Error>> {
    writeln!(writer, "Choose a Budget")?;

    let mut counter = 1;
    for budget in budgets {
        writeln!(writer, "{0}: {1}", counter, budget.name)?;
        counter += 1;
    }

    let input = input::<usize>().get();
    let selected_budget = budgets.get(input - 1).expect("error, invalid selection");

    Ok(&selected_budget.id)
}

pub async fn get_uncleared_transactions(
    client: &YnabClient,
    budget_id: &str,
) -> Result<Vec<YnabTransaction>, Box<dyn std::error::Error>> {
    let response: transaction_structure::Response = client
        .client
        .get(format!("{0}budgets/{1}/transactions", API, &budget_id))
        .bearer_auth(&client.apikey)
        .send()
        .await?
        .json()
        .await?;

    let transactions: Vec<transaction_structure::Transaction> = response
        .data
        .transactions
        .into_iter()
        .filter(|transaction| !transaction.approved)
        .collect();
    let mut ynab_transactions = Vec::<YnabTransaction>::new();
    for transaction in transactions {
        let ynab_money = YnabMoney {
            amount: transaction.amount,
        };
        let ynab_transaction = YnabTransaction {
            transaction: transaction,
            amount: ynab_money,
        };

        ynab_transactions.push(ynab_transaction);
    }
    Ok(ynab_transactions)
}

pub fn choose_transaction(
    transactions: &[YnabTransaction],
    mut writer: impl std::io::Write,
) -> Result<YnabTransaction, Box<dyn std::error::Error>> {
    let mut counter = 1;
    for transaction in transactions {
        writeln!(
            writer,
            "{0}: {1} {2}",
            counter, transaction.transaction.payee_name, transaction.amount
        )?;
        counter += 1;
    }
    writeln!(writer, "Please select a transaction")?;
    let input = input::<usize>().get();
    let selected_transaction = transactions
        .get(input - 1)
        .expect("error: invalid selection");
    let transaction = selected_transaction.clone();
    Ok(transaction)
}
#[allow(dead_code)]
async fn get_payee_name(
    client: &YnabClient,
    budget_id: &str,
    payee_id: &str,
) -> Result<payee_structure::Payee, Box<dyn std::error::Error>> {
    let response: payee_structure::Response = client
        .client
        .get(format!(
            "{0}budgets/{1}/payees/{2}",
            API, &budget_id, &payee_id
        ))
        .bearer_auth(&client.apikey)
        .send()
        .await?
        .json()
        .await?;

    Ok(response.data.payee)
}

pub async fn choose_category_group(
    client: &YnabClient,
    budget_id: &str,
    writer: &mut impl std::io::Write,
) -> Result<category_structure::CategoryGroup, Box<dyn std::error::Error>> {
    let response: category_structure::Response = client
        .client
        .get(format!("{0}budgets/{1}/categories", API, &budget_id))
        .bearer_auth(&client.apikey)
        .send()
        .await?
        .json()
        .await?;

    let mut counter = 1;
    for category in &response.data.category_groups {
        writeln!(writer, "{0}: {1}", counter, category.name)?;
        counter += 1;
    }
    writeln!(writer, "Please select a category group")?;
    let input = input::<usize>().get();
    let selected_category = response
        .data
        .category_groups
        .get(input - 1)
        .expect("error: invalid selection");
    Ok(selected_category.clone())
}

pub fn choose_category_from_group(
    category_group: &category_structure::CategoryGroup,
    writer: &mut impl std::io::Write,
) -> Result<category_structure::Category, Box<dyn std::error::Error>> {
    let mut counter = 1;
    for category in &category_group.categories {
        writeln!(writer, "{0}: {1}", counter, category.name)?;
        counter += 1;
    }

    writeln!(writer, "Please select a category")?;
    let input = input::<usize>().get();
    let selected_category = category_group
        .categories
        .get(input - 1)
        .expect("error: invalid selection");
    Ok(selected_category.clone())
}

pub async fn update_category_for_transaction(
    client: &YnabClient,
    category: &category_structure::Category,
    budget_id: &str,
    transaction: &mut YnabTransaction,
) -> Result<(), Box<dyn std::error::Error>> {
    transaction.transaction.category_id = Some(String::from(&category.id[..]));
    transaction.transaction.category_name = String::from(&category.name[..]);
    transaction.transaction.approved = true;

    let request = transaction_structure::Request {
        transaction: transaction.transaction.clone(),
    };
    client
        .client
        .put(format!(
            "{0}budgets/{1}/transactions/{2}",
            API, &budget_id, &transaction.transaction.id
        ))
        .bearer_auth(&client.apikey)
        .json(&request)
        .send()
        .await?;

    Ok(())
}
