use std::fmt::Display;

use std::time::Duration;

use crate::cycle::Cycle;
use crate::envelope::{current_time, Transaction};
use crate::target::SavingGoal;
use euro::{Currency, Euros};

use crate::euro;
use crate::{bank::Bank, envelope::Envelope};

pub fn run() {
    let mut bank = Bank::load();
    loop {
        bank_info(&bank);
        let mut envelopes = Envelope::fetch_all();
        let msg = if envelopes.is_empty() {
            "\nadd new envelope by writing 'n'"
        } else {
            ""
        };
        view_envelopes(&bank, msg);
        action(&mut bank, &mut envelopes);
    }
}

pub enum Color {
    Red,
    Green,
    Yellow,
}

impl Color {
    const RESET: &str = "\x1b[0m";
}

impl Display for Color {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let color_code = match self {
            Color::Red => "\x1b[31m",
            Color::Green => "\x1b[32m",
            Color::Yellow => "\x1b[33m",
        };
        write!(f, "{}", color_code)
    }
}

fn println_with_color(text: &str, color: Color) {
    print_with_color(text, color);
    println!();
}

fn print_with_color(text: &str, color: Color) {
    print!("{}{}{}", color, text, Color::RESET)
}

fn clear_window() {
    print!("\x1B[2J\x1B[1;1H");
}

fn view_envelopes(bank: &Bank, msg: &str) {
    let envelopes = Envelope::fetch_all();
    println!("{msg}");
    envelope_printer(bank, &envelopes);
}

pub fn envelope_printer(bank: &Bank, envelopes: &[Envelope]) {
    for (idx, envelope) in envelopes.iter().enumerate() {
        let s = format!(
            "{}: {} {}-> {}\n",
            idx,
            envelope.name,
            envelope.assigned(bank).print(),
            envelope.goal.display(bank, envelope)
        );

        let color = envelope.goal.color_status(bank, envelope.id());
        println_with_color(s.as_str(), color);
    }
}

fn get_input_string() -> String {
    let mut input = String::new();
    std::io::stdin()
        .read_line(&mut input)
        .expect("Failed to read line");
    input.pop();
    input
}

pub fn index_splitter(s: String) -> (Option<usize>, String) {
    match s.find(|c: char| !c.is_numeric()) {
        Some(pos) if pos != 0 => {
            let (index, rest) = s.split_at(pos);
            (index.parse::<usize>().unwrap().into(), rest.into())
        }
        _ => match s.parse::<usize>() {
            Ok(index) => (index.into(), "".into()),
            Err(_) => (None, s),
        },
    }
}

fn msg(msg: &str) {
    clear_window();
    println!("{}", msg);
}

fn msg_with_break(msg: &str) {
    clear_window();
    println!("{}", msg);
    get_input_string();
}

fn inspect_envelope(envelope: &Envelope) {
    let s = format!("{:?}", envelope);
    msg_with_break(s.as_str());
}

fn pick_envelope(bank: &Bank, msg: &str) -> Envelope {
    let envelopes = &Envelope::fetch_all();
    if envelopes.is_empty() {
        panic!();
    }

    clear_window();
    view_envelopes(bank, msg);
    loop {
        let Ok(index) = get_input_string().parse::<usize>() else {continue};
        if let Some(envelope) = envelopes.get(index) {
            return envelope.to_owned();
        }
    }
}

fn howmanydays(message: &str) -> Duration {
    msg(message);

    loop {
        let input = get_input_string();
        if let Ok(qty) = input.parse::<f32>() {
            return Duration::from_secs_f32(qty * 86400.);
        }
    }
}

fn howmanyeuros(message: &str) -> Euros {
    msg(message);

    loop {
        let input = get_input_string();
        if let Ok(qty) = Currency::try_from(input) {
            return Euros::from(qty);
        }
    }
}

fn choose_goal() -> Option<SavingGoal> {
    msg("Choose goal for envelope");
    let goals: Vec<&str> = vec!["none", "amount", "saving", "spending", "saving building"];

    for (idx, goal) in goals.iter().enumerate() {
        println!("{} -> {}", idx, goal);
    }

    let idx = get_input_string().parse::<usize>().ok()?;

    match idx {
        0 => Some(SavingGoal::None),
        1 => {
            let amount = howmanyeuros("Amount of euros?");
            Some(SavingGoal::Amount(amount))
        }
        2 => {
            let start = current_time();
            let end = current_time() + howmanydays("how many days until this goal?");
            let amount = howmanyeuros("How many euros is the goal?");
            Some(SavingGoal::SavingGoal { start, end, amount })
        }
        3 => {
            println!("0 weekly\n1 monthly");
            let cycle = match get_input_string().trim() {
                "0" => Cycle::Week,
                "1" => Cycle::Month,
                _ => return None,
            };
            let amount = howmanyeuros("saving goals");

            Some(SavingGoal::Spending { cycle, amount })
        }
        4 => {
            let amount = howmanyeuros("How much money per month do you wanna save up?");
            Some(SavingGoal::SavingBuilding { amount })
        }
        _ => None,
    }
}

fn action_on_envelope(bank: &mut Bank, envelope: &mut Envelope, action: &str) {
    match action.trim() {
        "a" => {
            msg("How much money to assign?");
            let Ok(input) = get_input_string().parse::<f32>() else {return};
            let amount: Euros = input.into();
            bank.assign_money(envelope.id(), amount);
        }
        "A" => {
            msg("How much money to remove from assignment?");
            let Ok(input) = get_input_string().parse::<f32>() else {return};
            let amount: Euros = input.into();
            bank.remove_assign(envelope.id(), amount);
        }
        "t" => {
            msg("how many euros is this transaction?");

            let Ok(currency) = Currency::try_from(get_input_string()) else {return};
            let trans = Transaction::new(currency, envelope.id());
            bank.new_transaction(trans);
        }

        "m" => {
            let new_envelope = pick_envelope(bank, "which envelope to move money to?");
            if envelope.id() != new_envelope.id() {
                msg("how many euros to move?");
                let Ok(input) = get_input_string().parse::<f32>() else {return};
                let amount: Euros = input.into();
                bank.remove_assign(envelope.id(), amount);
                bank.assign_money(new_envelope.id(), amount);
            }
        }
        "g" => {
            if let Some(goal) = choose_goal() {
                envelope.goal = goal;
                envelope.persist();
            }
        }
        "d" => {
            envelope.clone().delete();
            msg("envelope deleted :O ");
            bank.clear_assign(envelope.id());
        }
        "" => inspect_envelope(envelope),
        _ => {}
    }
}

fn action_no_envelope(action: &str, bank: &mut Bank, _envelopes: &[Envelope]) {
    match action {
        "i" => {
            msg("insert money to bank!");
            if let Ok(money) = get_input_string().parse::<f32>() {
                let money: Euros = money.into();
                bank.insert_money(money);
            }
        }
        "I" => {
            msg("Remove money from bank :(");
            if let Ok(money) = get_input_string().parse::<f32>() {
                let money: Euros = money.into();
                bank.remove_money(money);
            }
        }
        "n" => {
            msg("add new envelope");
            let name = get_input_string();
            let envelope = Envelope::new_empty(&name);
            envelope.persist();
        }
        _ => {}
    }
}

pub fn action(bank: &mut Bank, envelopes: &mut [Envelope]) {
    let input = get_input_string();
    let (index, rest) = index_splitter(input);

    match index {
        Some(index) => {
            let Some(envelope) = envelopes.get_mut(index)  else {return};
            action_on_envelope(bank, envelope, rest.as_str());
        }
        None => action_no_envelope(rest.as_str(), bank, envelopes),
    }
}

pub fn bank_info(bank: &Bank) {
    clear_window();

    println!(
        "total money: {}\nUnassigned: {}",
        bank.total_money().euros(),
        bank.unassigned().euros()
    );
}