pub mod transaction;
pub mod transactions;

use std::{error::Error, fmt::Display, path::PathBuf, string::ToString};

use chrono::{DateTime, NaiveDate, ParseError, TimeDelta, TimeZone, Utc};
use iced::{
    Length,
    widget::{Button, Row, Scrollable, TextInput, button, column, row, text, text_input},
};
use plotters_iced2::ChartWidget;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use serde::{Deserialize, Serialize};
use transactions::{PriceAsTransaction, Transactions};

use crate::app::{self, EDGE_PADDING, PADDING, account::transaction::Transaction};

use super::{
    Duration, ROW_SPACING, button_cell,
    chart::Chart,
    import_boa::import_boa,
    money::{Currency, Fiat},
    number_cell,
    screen::Screen,
    set_amount, some_or_empty, text_cell,
};

#[derive(Debug, Deserialize, Serialize)]
pub struct Account {
    #[serde(skip)]
    pub check_box: bool,
    #[serde(skip)]
    pub duration: Duration,
    pub name: String,
    #[serde(skip)]
    pub tx: transaction::ToSubmit,
    #[serde(rename = "transactions")]
    pub txs_1st: Transactions<Fiat>,
    #[serde(rename = "transactions_secondary")]
    pub txs_2nd: Option<Transactions<Currency>>,
    #[serde(skip)]
    pub filter_date: Option<DateTime<Utc>>,
    #[serde(skip)]
    pub filter_date_year: Option<i32>,
    #[serde(skip)]
    pub filter_date_month: Option<u32>,
    #[serde(skip)]
    pub error: Option<String>,
}

impl Account {
    pub fn new(name: String, currency: Currency) -> Self {
        let (txs_1st, txs_2nd) = match &currency {
            Currency::StockPlus(_) => (
                Transactions::new(Fiat::Usd),
                Some(Transactions::new(currency)),
            ),
            Currency::Crypto(crypto) => (
                Transactions::new(crypto.currency.clone()),
                Some(Transactions::new(currency)),
            ),
            Currency::Metal(metal) => (
                Transactions::new(metal.currency.clone()),
                Some(Transactions::new(currency)),
            ),
            Currency::Fiat(currency) => (Transactions::new(currency.clone()), None),
        };

        Self {
            check_box: false,
            duration: Duration::default(),
            name,
            tx: transaction::ToSubmit::new(),
            txs_1st,
            txs_2nd,
            filter_date: None,
            filter_date_year: None,
            filter_date_month: None,
            error: None,
        }
    }

    pub fn balance_1st(&self) -> Decimal {
        self.txs_1st.balance()
    }

    pub fn balance_2nd(&self) -> Option<Decimal> {
        self.txs_2nd
            .as_ref()
            .map(transactions::Transactions::balance)
    }

    fn clear_date(&mut self) {
        self.filter_date_year = None;
        self.filter_date_month = None;
        self.filter_date = None;
    }

    fn get_quantity(&self, date: DateTime<Utc>) -> Option<Transaction> {
        if let Some(txs) = &self.txs_2nd {
            if txs.txs.is_empty() {
                return None;
            }

            if txs.txs.len() == 1 {
                let tx = txs.txs.first().unwrap();
                return if tx.date <= date {
                    Some(tx.clone())
                } else {
                    None
                };
            }

            for window in txs.txs.windows(2) {
                if window[1].date > date {
                    return Some(window[0].clone());
                }
            }

            let tx = txs.txs.last().unwrap();
            if tx.date <= date {
                return Some(tx.clone());
            }

            None
        } else {
            None
        }
    }

    pub fn import_boa(&mut self, file_path: PathBuf) -> anyhow::Result<()> {
        let mut boa = import_boa(file_path)?;
        boa.remove_duplicates(&self.txs_1st);
        boa.sort();

        if let Some(tx_1st) = self.txs_1st.txs.last() {
            if let Some(tx_add) = boa.txs.first() {
                if tx_1st.date > tx_add.date {
                    return Err(anyhow::Error::msg(
                        "The starting date of the first transaction you want to add is not greater than the end date of the old transactions.",
                    ));
                }
            } else {
                return Ok(());
            }
        }

        let mut balance = self.txs_1st.balance();
        for mut tx in boa.txs {
            balance += tx.amount;
            tx.balance = balance;
            self.txs_1st.txs.push(tx);
        }
        self.tx = transaction::ToSubmit::new();
        Ok(())
    }

    fn input(&self) -> Row<'_, super::Message> {
        row![
            balance_view(self.tx.balance.as_ref()),
            amount_view(self.tx.amount.as_ref()),
            date_view(&self.tx.date),
            comment_view(&self.tx.comment),
            add_view(self.tx.amount.as_ref(), self.tx.balance.as_ref()),
            text(" ".repeat(EDGE_PADDING)),
        ]
        .padding(PADDING)
        .spacing(ROW_SPACING)
    }

    fn filter_date(&self) -> Row<'_, super::Message> {
        let year = text_input("Year", &some_or_empty(self.filter_date_year.as_ref()))
            .on_input(|string| app::Message::Account(Message::ChangeFilterDateYear(string)));

        let month = text_input("Month", &some_or_empty(self.filter_date_month.as_ref()))
            .on_input(|string| app::Message::Account(Message::ChangeFilterDateMonth(string)));

        let mut filter_button = button("Filter");
        if self.submit_filter_date().is_some() {
            filter_button =
                filter_button.on_press(app::Message::Account(Message::SubmitFilterDate));
        }

        let clear_button = button("Clear").on_press(app::Message::Account(Message::ClearDate));
        row![
            year,
            month,
            filter_button,
            clear_button,
            text(" ".repeat(EDGE_PADDING)),
        ]
        .padding(PADDING)
        .spacing(ROW_SPACING)
    }

    pub fn list_transactions_2nd(&self) -> Scrollable<'_, app::Message> {
        let mut txs_struct = self.txs_2nd.as_ref().unwrap().clone();
        txs_struct.filter_month(self.filter_date);

        let chart = Chart {
            txs: txs_struct.clone(),
            duration: self.duration.clone(),
        };
        let chart = ChartWidget::new(chart).height(Length::Fixed(400.0));

        let mut col_1 = column![text_cell("Balance")].align_x(iced::Alignment::End);
        let mut col_2 = column![text_cell("Δ")].align_x(iced::Alignment::End);
        let mut col_3 = column![text_cell("Date")];
        let mut col_4 = column![text_cell("Comment")];
        let mut col_5 = column![text_cell("")];

        for (i, tx) in txs_struct.txs.iter().enumerate() {
            let mut balance = tx.balance;
            let mut amount = tx.amount;
            balance.rescale(10);
            amount.rescale(10);

            col_1 = col_1.push(number_cell(balance));
            col_2 = col_2.push(number_cell(amount));
            col_3 = col_3.push(text_cell(tx.date.format("%Y-%m-%d").to_string()));
            col_4 = col_4.push(text_cell(tx.comment.clone()));
            col_5 = col_5.push(button_cell(
                button("Delete").on_press(app::Message::Delete(i)),
            ));
        }
        let rows = row![col_1, col_2, col_3, col_4, col_5];

        let error = self
            .error
            .as_ref()
            .map_or_else(|| row![], |error| row![text_cell(error)]);

        let col = column![
            text_cell(txs_struct.currency.to_string()),
            chart,
            change_duration(),
            rows.spacing(ROW_SPACING),
            row![
                text_cell("balance: "),
                number_cell(self.balance_2nd().unwrap()),
                text_cell("total: "),
                number_cell(self.total_2nd())
            ],
            self.input(),
            self.filter_date(),
            error,
            back_exit_view(),
        ];

        Scrollable::new(col)
    }

    fn rows(&self, txs_1st: &Transactions<Fiat>) -> Row<'_, super::Message> {
        let mut col_1 = column![text_cell("Balance")].align_x(iced::Alignment::End);
        let mut col_2 = column![text_cell("Δ")].align_x(iced::Alignment::End);
        let mut col_3 = column![text_cell("Price")].align_x(iced::Alignment::End);
        let mut col_3b = column![text_cell("Δ")].align_x(iced::Alignment::End);
        let mut col_4 = column![text_cell("Quantity")].align_x(iced::Alignment::End);
        let mut col_4b = column![text_cell("Δ")].align_x(iced::Alignment::End);
        let mut col_5 = column![text_cell("Date")];
        let mut col_6 = column![text_cell("Comment")];
        let mut col_7 = column![text_cell("")];

        let mut quantity = dec!(0);
        let mut price = dec!(0);
        for (i, tx) in txs_1st.txs.iter().enumerate() {
            let mut balance = tx.balance;
            let mut amount = tx.amount;

            if let Some(tx_quantity) = self.get_quantity(tx.date) {
                let mut quantity_new = tx_quantity.balance;
                let mut price_new = balance / quantity_new;
                let mut quantity_diff = quantity_new - quantity;
                let mut price_diff = price_new - price;
                quantity = quantity_new;
                price = price_new;

                price_new.rescale(2);
                price_diff.rescale(2);
                quantity_new.rescale(8);
                quantity_diff.rescale(8);
                col_3 = col_3.push(number_cell(price_new));
                col_3b = col_3b.push(number_cell(price_diff));
                col_4 = col_4.push(number_cell(quantity_new));
                col_4b = col_4b.push(number_cell(quantity_diff));
            } else {
                col_3 = col_3.push(text_cell(""));
                col_3b = col_3b.push(text_cell(""));
                col_4 = col_4.push(text_cell(""));
                col_4b = col_4b.push(text_cell(""));
            }

            balance.rescale(2);
            amount.rescale(2);
            col_1 = col_1.push(number_cell(balance));
            col_2 = col_2.push(number_cell(amount));

            col_5 = col_5.push(text_cell(tx.date.format("%Y-%m-%d").to_string()));
            col_6 = col_6.push(text_cell(tx.comment.clone()));
            col_7 = col_7.push(button_cell(
                button("Delete").on_press(app::Message::Delete(i)),
            ));
        }

        let rows = if self.txs_2nd.is_some() {
            row![
                col_1, col_2, col_3, col_3b, col_4, col_4b, col_5, col_6, col_7
            ]
        } else {
            row![col_1, col_2, col_5, col_6, col_7]
        };

        rows.spacing(ROW_SPACING)
    }

    pub fn list_transactions(&self) -> Scrollable<'_, app::Message> {
        let mut txs_1st = self.txs_1st.clone();
        txs_1st.filter_month(self.filter_date);

        let chart = Chart {
            txs: txs_1st.clone(),
            duration: self.duration.clone(),
        };
        let chart: ChartWidget<_, _, _, _> = ChartWidget::new(chart).height(Length::Fixed(400.0));

        let error = self
            .error
            .as_ref()
            .map_or_else(|| row![], |error| row![text_cell(error)]);

        let name = if let Some(txs_2nd) = &self.txs_2nd {
            txs_2nd.currency.to_string()
        } else {
            self.txs_1st.currency.to_string()
        };

        let col = column![
            text_cell(name),
            chart,
            change_duration(),
            self.rows(&txs_1st),
            row![
                text_cell("balance: "),
                number_cell(txs_1st.balance()),
                text_cell("total: "),
                number_cell(txs_1st.total()),
            ]
            .spacing(ROW_SPACING),
            self.input(),
            self.filter_date(),
            error,
            back_exit_view(),
        ];

        Scrollable::new(col)
    }

    fn parse_date(&self) -> Result<DateTime<Utc>, ParseDateError> {
        if self.tx.date.is_empty() {
            Ok(Utc::now())
        } else {
            match NaiveDate::parse_from_str(&self.tx.date, "%Y-%m-%d") {
                Ok(naive_date) => Ok(naive_date.and_hms_opt(0, 0, 0).unwrap().and_utc()),
                Err(error) => Err(ParseDateError { error }),
            }
        }
    }

    fn submit_filter_date(&self) -> Option<DateTime<Utc>> {
        let year = self.filter_date_year?;
        let month = self.filter_date_month?;

        Some(TimeZone::with_ymd_and_hms(&Utc, year, month, 1, 0, 0, 0).unwrap())
    }

    fn submit_balance_1st(&mut self) -> anyhow::Result<Transaction> {
        let balance = self.tx.balance.unwrap();
        let date = self.parse_date()?;

        let tx = Transaction {
            amount: dec!(0),
            balance,
            comment: self.tx.submit_commit(),
            date,
        };
        Ok(self.txs_1st.balance_to_amount(tx))
    }

    fn submit_balance_2nd(&mut self) -> anyhow::Result<Transaction> {
        let balance = self.tx.balance.unwrap();
        let date = self.parse_date()?;

        let tx = Transaction {
            amount: dec!(0),
            balance,
            comment: self.tx.submit_commit(),
            date,
        };
        Ok(self.txs_2nd.as_mut().unwrap().balance_to_amount(tx))
    }

    pub async fn submit_price_as_transaction(&self) -> anyhow::Result<Transaction> {
        let mut tx = self
            .txs_2nd
            .as_ref()
            .unwrap()
            .get_price_as_transaction()
            .await?;
        tx.amount = tx.balance - self.balance_1st();
        Ok(tx)
    }

    fn submit_tx_1st(&self) -> anyhow::Result<Transaction> {
        let amount = self.tx.amount.unwrap();
        let date = self.parse_date()?;
        self.txs_1st.date_most_recent(&date)?;

        Ok(Transaction {
            amount,
            balance: self.balance_1st() + amount,
            comment: self.tx.submit_commit(),
            date,
        })
    }

    fn submit_tx_2nd(&self) -> anyhow::Result<Transaction> {
        let amount = self.tx.amount.unwrap();
        let date = self.parse_date()?;
        self.txs_2nd.as_ref().unwrap().date_most_recent(&date)?;

        Ok(Transaction {
            amount,
            balance: self.balance_2nd().unwrap() + amount,
            comment: self.tx.submit_commit(),
            date,
        })
    }

    pub fn total_2nd(&self) -> Decimal {
        self.txs_2nd.as_ref().unwrap().total()
    }

    pub fn sum_last_week(&self) -> (Decimal, Decimal) {
        let last_week = Utc::now() - TimeDelta::weeks(1);
        let mut previous_amount = dec!(0);
        let mut amount = dec!(0);

        for tx in &self.txs_1st.txs {
            if tx.date >= last_week {
                amount += tx.amount;
            } else {
                previous_amount += tx.amount;
            }
        }
        (previous_amount, amount)
    }

    pub fn sum_last_month(&self) -> (Decimal, Decimal) {
        let last_month = Utc::now() - TimeDelta::days(30);
        let mut amount = dec!(0);
        let mut previous_amount = dec!(0);

        for tx in &self.txs_1st.txs {
            if tx.date >= last_month {
                amount += tx.amount;
            } else {
                previous_amount += tx.amount;
            }
        }
        (previous_amount, amount)
    }

    pub fn sum_last_year(&self) -> (Decimal, Decimal) {
        let last_year = Utc::now() - TimeDelta::days(365);
        let mut amount = dec!(0);
        let mut previous_amount = dec!(0);

        for tx in &self.txs_1st.txs {
            if tx.date >= last_year {
                amount += tx.amount;
            } else {
                previous_amount += tx.amount;
            }
        }
        (previous_amount, amount)
    }

    fn display_error(&mut self, result: anyhow::Result<Transaction>) -> Option<Transaction> {
        match result {
            Ok(tx) => Some(tx),
            Err(error) => {
                self.error = Some(error.to_string());
                None
            }
        }
    }

    pub fn update(&mut self, screen: &Screen, message: Message) -> bool {
        self.error = None;

        match message {
            Message::ChangeBalance(balance) => {
                set_amount(&mut self.tx.balance, &balance);
            }
            Message::ChangeComment(comment) => self.tx.comment = comment,
            Message::ChangeDate(date) => self.tx.date = date,
            Message::ChangeFilterDateMonth(date) => {
                if date.is_empty() {
                    self.filter_date_month = None;
                }
                if let Ok(date) = date.parse()
                    && (1..13).contains(&date)
                {
                    self.filter_date_month = Some(date);
                }
            }
            Message::ChangeFilterDateYear(date) => {
                if date.is_empty() {
                    self.filter_date_year = None;
                }
                if let Ok(date) = date.parse()
                    && (0..3_000).contains(&date)
                {
                    self.filter_date_year = Some(date);
                }
            }
            Message::ChangeTx(tx) => set_amount(&mut self.tx.amount, &tx),
            Message::ChartWeek => self.duration = Duration::Week,
            Message::ChartMonth => self.duration = Duration::Month,
            Message::ChartYear => self.duration = Duration::Year,
            Message::ChartAll => self.duration = Duration::All,
            Message::ClearDate => self.clear_date(),
            Message::SubmitBalance => match screen {
                Screen::Account(_) => {
                    let result = self.submit_balance_1st();
                    if let Some(tx) = self.display_error(result) {
                        self.txs_1st.txs.push(tx);
                        self.txs_1st.sort();
                        self.tx = transaction::ToSubmit::new();
                        return true;
                    }
                }
                Screen::AccountSecondary(_) => {
                    let result = self.submit_balance_2nd();
                    if let Some(tx) = self.display_error(result) {
                        self.txs_2nd.as_mut().unwrap().txs.push(tx);
                        self.txs_2nd.as_mut().unwrap().sort();
                        self.tx = transaction::ToSubmit::new();
                        return true;
                    }
                }
                Screen::Accounts | Screen::Configuration => {
                    panic!("You can't submit a balance here!");
                }
            },
            Message::SubmitFilterDate => {
                self.filter_date = self.submit_filter_date();
            }
            Message::SubmitTx => match screen {
                Screen::Account(_) => {
                    if let Some(tx) = self.display_error(self.submit_tx_1st()) {
                        self.txs_1st.txs.push(tx);
                        self.txs_1st.sort();
                        self.tx = transaction::ToSubmit::new();
                        return true;
                    }
                }
                Screen::AccountSecondary(_) => {
                    if let Some(tx) = self.display_error(self.submit_tx_2nd()) {
                        self.txs_2nd.as_mut().unwrap().txs.push(tx);
                        self.txs_2nd.as_mut().unwrap().sort();
                        self.tx = transaction::ToSubmit::new();
                        return true;
                    }
                }
                Screen::Accounts | Screen::Configuration => {
                    panic!("You can't submit a transaction here!")
                }
            },
        }
        false
    }
}

fn amount_view(amount: Option<&Decimal>) -> TextInput<'_, app::Message> {
    text_input("Amount", &some_or_empty(amount))
        .on_input(|string| app::Message::Account(Message::ChangeTx(string)))
}

fn balance_view(balance: Option<&Decimal>) -> TextInput<'_, app::Message> {
    text_input("Balance", &some_or_empty(balance))
        .on_input(|string| app::Message::Account(Message::ChangeBalance(string)))
}

fn date_view(date: &str) -> TextInput<'_, app::Message> {
    text_input("Date YYYY-MM-DD (empty for today)", date)
        .on_input(|string| app::Message::Account(Message::ChangeDate(string)))
}

fn comment_view(comment: &str) -> TextInput<'_, app::Message> {
    text_input("Comment", comment)
        .on_input(|string| app::Message::Account(Message::ChangeComment(string)))
        .on_paste(|string| app::Message::Account(Message::ChangeComment(string)))
}

fn add_view<'a>(amount: Option<&Decimal>, balance: Option<&Decimal>) -> Button<'a, app::Message> {
    let mut add = button("Add");
    match (amount, balance) {
        (Some(_amount), None) => add = add.on_press(app::Message::Account(Message::SubmitTx)),
        (None, Some(_balance)) => {
            add = add.on_press(app::Message::Account(Message::SubmitBalance));
        }
        (None, None) | (Some(_), Some(_)) => {}
    }
    add
}

fn back_exit_view<'a>() -> Row<'a, app::Message> {
    row![
        button("Back").on_press(app::Message::Back),
        button("Exit").on_press(app::Message::Exit),
    ]
    .spacing(ROW_SPACING)
}

fn change_duration<'a>() -> Row<'a, app::Message> {
    let col_1 = button("Week").on_press(app::Message::Account(Message::ChartWeek));
    let col_2 = button("Month").on_press(app::Message::Account(Message::ChartMonth));
    let col_3 = button("Year").on_press(app::Message::Account(Message::ChartYear));
    let col_4 = button("All").on_press(app::Message::Account(Message::ChartAll));

    row![col_1, col_2, col_3, col_4].spacing(ROW_SPACING)
}

#[derive(Clone, Debug)]
pub struct ParseDateError {
    error: ParseError,
}

impl Display for ParseDateError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
        writeln!(f, "Parse Date error: {}", self.error)
    }
}

impl Error for ParseDateError {}

#[derive(Clone, Debug)]
pub enum Message {
    ChangeBalance(String),
    ChangeComment(String),
    ChangeDate(String),
    ChangeFilterDateMonth(String),
    ChangeFilterDateYear(String),
    ChangeTx(String),
    ChartWeek,
    ChartMonth,
    ChartYear,
    ChartAll,
    ClearDate,
    SubmitBalance,
    SubmitFilterDate,
    SubmitTx,
}