// assets_managing_team manages [asset_manage_announcement], [kelly_criterion], and[wallet] ([wallet_testnet] as well)

use crate::coex::exchange_team::*;
use crate::coex::order_team::{DBlist, SellHistoryList};
use crate::database_control::*;
use crate::decimal_funcs::*;
use crate::RunningMode::*;
use crate::RUNNING_MODE;
use log;
use reqwest::Client;
use rust_decimal::{prelude::ToPrimitive, Decimal, RoundingStrategy};
use rust_decimal_macros::dec;
use serde_json::{Result, Value};
use sqlx::FromRow;

#[derive(Debug, FromRow)]
pub struct AchievementEvaluationInfo {
    pub strategist: u16,
    pub invested_usdt: Decimal,
    pub usdt_profit: Decimal,
    pub profit_percent: f64,
}

impl DBlist for AchievementEvaluationInfo {
    fn new() -> AchievementEvaluationInfo {
        let a = AchievementEvaluationInfo {
            strategist: 0,
            invested_usdt: Decimal::new(0, 8),
            usdt_profit: Decimal::new(0, 8),
            profit_percent: 0.0,
        };
        a
    }
}

#[derive(Debug, FromRow)]
pub struct AssetInfo {
    pub initial_usdt: Decimal,
    pub current_total_usdt: Decimal,
    pub profit: Decimal,
    pub realtime_expected_total_usdt: f64,
    pub realtime_profit: f64,
    pub available_usdt: Decimal,
    pub is_tradable: u8,
    pub unit_trade_usdt: Decimal,
}

impl DBlist for AssetInfo {
    fn new() -> AssetInfo {
        let a = AssetInfo {
            initial_usdt: Decimal::new(0, 8),
            current_total_usdt: Decimal::new(0, 8),
            profit: Decimal::new(0, 8),
            realtime_expected_total_usdt: 0.0,
            realtime_profit: 0.0,
            available_usdt: Decimal::new(0, 8),
            is_tradable: 0,
            unit_trade_usdt: Decimal::new(0, 8),
        };
        a
    }
}

#[derive(Debug, FromRow)]
pub struct WalletInfo {
    pub asset: String,
    pub free: Decimal,
    pub locked: Decimal,
}

impl DBlist for WalletInfo {
    fn new() -> WalletInfo {
        let a = WalletInfo {
            asset: String::new(),
            free: Decimal::new(0, 8),
            locked: Decimal::new(0, 8),
        };
        a
    }
}

#[derive(Debug, FromRow)]
pub struct KellyCriterionInfo {
    pub betting_rate: f64,
    pub win_rate: f64,
    pub lose_rate: f64,
    pub profit_rate: f64,
    pub loss_rate: f64,
}

impl DBlist for KellyCriterionInfo {
    fn new() -> KellyCriterionInfo {
        let a = KellyCriterionInfo {
            betting_rate: 0.0,
            win_rate: 0.0,
            lose_rate: 0.0,
            profit_rate: 0.0,
            loss_rate: 0.0,
        };
        a
    }
}

// set unit_trade_usdt
pub async fn set_unit_usdt() {
    let asset_info = select_asset_manage_announcement().await;
    let mut set_unit_trade_usdt = Decimal::new(0, 8);

    set_unit_trade_usdt = dec!(10.0); // $50 for each trade

    // update fields in [asset_manage_announcement] table
    let update_table_name = String::from("asset_manage_announcement");
    let update_values = vec![(
        String::from("unit_trade_usdt"),
        set_unit_trade_usdt.to_string(),
    )];
    let update_condition = vec![(String::from("id"), String::from("1"))];
    update_record3(&update_table_name, &update_values, &update_condition)
        .await
        .unwrap();
}

// add extra usdt to initial usdt
pub async fn add_extra_usdt(extra_usdt: Decimal) {
    // update fields in [asset_manage_announcement] table
    let update_table_name = String::from("asset_manage_announcement");
    let mut value_build = String::from("initial_usdt + ");
    value_build.push_str(
        extra_usdt
            .round_dp_with_strategy(8, RoundingStrategy::ToZero)
            .to_string()
            .as_str(),
    );

    let update_values = vec![(String::from("initial_usdt"), value_build)];
    let update_condition = vec![(String::from("id"), String::from("1"))];
    update_record3(&update_table_name, &update_values, &update_condition)
        .await
        .unwrap();

    let mut value_build = String::from("current_total_usdt + ");
    value_build.push_str(
        extra_usdt
            .round_dp_with_strategy(8, RoundingStrategy::ToZero)
            .to_string()
            .as_str(),
    );

    let update_values = vec![(String::from("current_total_usdt"), value_build)];
    let update_condition = vec![(String::from("id"), String::from("1"))];
    update_record3(&update_table_name, &update_values, &update_condition)
        .await
        .unwrap();

    add_available_usdt(extra_usdt.round_dp_with_strategy(8, RoundingStrategy::ToZero)).await;

    // add additional_usdt into [wallet_simul]
    let mut update_table_name = String::new();
    unsafe {
        if RUNNING_MODE == SIMUL {
            let update_table_name = String::from("wallet");
            let mut value_build = String::from("free + ");
            value_build.push_str(extra_usdt.to_string().as_str());
            let update_values = vec![(String::from("free"), value_build)];
            let update_condition = vec![(String::from("asset"), String::from("USDT"))];
            update_record3(&update_table_name, &update_values, &update_condition)
                .await
                .unwrap();
        }
    }

    println!("add_extra_usdt 완료");
}

// update current_total_usdt and profit of asset
pub async fn update_current_total_usdt() {
    let mut update_values: Vec<(String, String)> = Vec::new();
    unsafe {
        if RUNNING_MODE == REAL || RUNNING_MODE == TEST {
            let free_usdt = fetch_free_usdt().await;
            let initial_usdt = select_asset_manage_announcement().await.initial_usdt;
            // println!("update_current_total_usdt실행 free_usdt: {}, initial_usdt: {}", free_usdt, initial_usdt);
            let mut profit: Decimal;
            if initial_usdt.is_zero() {
                profit = dec!(0.0);
            } else {
                profit = decimal_sub(decimal_div(free_usdt, initial_usdt), dec!(1));
            }

            update_values = vec![
                (String::from("current_total_usdt"), free_usdt.to_string()),
                (String::from("profit"), profit.to_string()),
                (
                    String::from("realtime_expected_total_usdt"),
                    0.0.to_string(),
                ),
                (String::from("realtime_profit"), 0.0.to_string()),
            ];
        } else {
            let asset_info = select_asset_manage_announcement().await;

            let achievement_evaluation = select_achievement_evaluation().await;
            let balance = decimal_sub(
                achievement_evaluation.usdt_profit,
                achievement_evaluation.invested_usdt,
            );
            let current_total_usdt = decimal_add(asset_info.initial_usdt, balance);

            let profit = decimal_sub(
                decimal_div(current_total_usdt, asset_info.initial_usdt),
                dec!(1),
            );

            update_values = vec![
                (
                    String::from("current_total_usdt"),
                    current_total_usdt.to_string(),
                ),
                (String::from("profit"), profit.to_string()),
                (
                    String::from("realtime_expected_total_usdt"),
                    0.0.to_string(),
                ),
                (String::from("realtime_profit"), 0.0.to_string()),
            ];
        }
    }

    let update_table_name = String::from("asset_manage_announcement");
    let update_condition = vec![(String::from("id"), String::from("1"))];
    update_record3(&update_table_name, &update_values, &update_condition)
        .await
        .unwrap();
}

// monitor whether [buy_ordered_coin_list] is empty to update current_total_usdt and available_usdt in [asset_manage_announcement]
pub async fn monitoring_asset_usdt(previous_result: &mut bool, client: &Client) -> Result<()> {
    let table_name = String::from("buy_ordered_coin_list");
    let condition = None;
    let buy_list_result = exists_record(&table_name, &condition).await;

    let table_name = String::from("sell_ordered_coin_list");
    let condition = None;
    let sell_list_result = exists_record(&table_name, &condition).await;

    if buy_list_result == false && sell_list_result == false {
        unsafe {
            if buy_list_result == false
                && sell_list_result == false
                && *previous_result == true
                && (RUNNING_MODE == TEST || RUNNING_MODE == REAL)
            {
                let mut result = crate::coex::order_team::account_information(&client).await; // (/api, Weight(IP) 10)
                loop {
                    if result.is_ok() {
                        break;
                    } else {
                        log::warn!("retry account_information()");
                        tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
                        result = crate::coex::order_team::account_information(&client).await;
                        // (/api, Weight(IP) 10)
                    }
                }
                // FIXME: 아래 함수들을 고쳐야함. 순환 오류 빠지는 듯
                update_current_total_usdt().await;
                set_available_usdt().await;
                set_unit_usdt().await;
            } else if buy_list_result == false
                && sell_list_result == false
                && *previous_result == true
                && (RUNNING_MODE == SIMUL)
            {
                update_current_total_usdt().await;
                set_is_tradable().await;
            }
        }
        *previous_result = false;
    } else {
        update_realtime_asset().await;
        *previous_result = true;
    }
    Ok(())
}

// update kelly_criterion table and kelly_betting_usdt
pub async fn update_kelly_criterion() -> Result<()> {
    let table_name = String::from("sell_history");
    let condition = None;
    let is_exist = exists_record(&table_name, &condition).await;

    if is_exist == true {
        let select_columns = String::from("*");
        let select_condition = None;
        let data_struct = SellHistoryList::new();

        let sell_history_vec = select_record(
            &table_name,
            &select_columns,
            &select_condition,
            &data_struct,
        )
        .await
        .unwrap();

        // FIXME: let least_number = 50;
        let least_number = 50; // the least number of sell hostory to calculate kelly criterion

        if sell_history_vec.len() >= least_number {
            let lose_collection_vec = sell_history_vec
                .iter()
                .filter(|&a| a.pure_profit_percent < 0.0)
                .collect::<Vec<_>>();
            let win_collection_vec = sell_history_vec
                .iter()
                .filter(|&a| a.pure_profit_percent >= 0.0)
                .collect::<Vec<_>>();
            let win_rate: f64 = (win_collection_vec.len() as f64) / (sell_history_vec.len() as f64);
            let lose_rate: f64 = 1.0 - win_rate;
            let loss_sum = lose_collection_vec
                .iter()
                .fold(0.0, |acc, x| acc + x.pure_profit_percent);
            let loss_rate_avg = (loss_sum * 0.01) / (lose_collection_vec.len() as f64) * -1.0;
            let profit_sum = win_collection_vec
                .iter()
                .fold(0.0, |acc, x| acc + x.pure_profit_percent);
            let profit_rate_avg = (profit_sum * 0.01) / (win_collection_vec.len() as f64);

            let betting_rate = (win_rate / loss_rate_avg) - (lose_rate / profit_rate_avg);

            // update kelly_criterion
            let update_table_name = String::from("kelly_criterion");
            let update_value = vec![
                (String::from("betting_rate"), betting_rate.to_string()),
                (String::from("win_rate"), win_rate.to_string()),
                (String::from("lose_rate"), lose_rate.to_string()),
                (String::from("profit_rate"), profit_rate_avg.to_string()),
                (String::from("loss_rate"), loss_rate_avg.to_string()),
            ];
            let update_condition = vec![(String::from("id"), String::from("1"))];
            update_record3(&update_table_name, &update_value, &update_condition)
                .await
                .unwrap();
        }
    }

    Ok(())
}

// update realtime_expected_total_usdt and realtime_profit
pub async fn update_realtime_asset() {
    let asset_info = select_asset_manage_announcement().await;
    let scoreboard_list = select_scoreboard().await;
    let scoreboard_short = scoreboard_list.first().unwrap();
    let scoreboard_long = scoreboard_list.last().unwrap();

    if scoreboard_short.total_number_of_coin != 0 || scoreboard_long.total_number_of_coin != 0 {
        // calculate expect_realtime_asset
        let available_usdt = asset_info.available_usdt.to_f64().unwrap();
        let total_used_usdt = scoreboard_short.total_used_usdt + scoreboard_long.total_used_usdt;
        let current_pos_profit_usdt =
            scoreboard_short.total_pos_profit_usdt + scoreboard_long.total_pos_profit_usdt;
        let current_neg_profit_usdt =
            scoreboard_short.total_neg_profit_usdt + scoreboard_long.total_neg_profit_usdt;
        let expect_realtime_expected_total_usdt =
            available_usdt + total_used_usdt + current_pos_profit_usdt + current_neg_profit_usdt;

        // calculate realtime_profit
        let mut realtime_profit = 0.0;
        let current_total_usdt = asset_info.current_total_usdt;
        if current_total_usdt.is_zero() {
            realtime_profit = 0.0;
        } else {
            realtime_profit = ((expect_realtime_expected_total_usdt
                / current_total_usdt.to_f64().unwrap())
                - 1.0)
                * 100.0;
        }

        // update record
        let update_table_name = String::from("asset_manage_announcement");
        let update_values = vec![
            (
                String::from("realtime_expected_total_usdt"),
                expect_realtime_expected_total_usdt.to_string(),
            ),
            (String::from("realtime_profit"), realtime_profit.to_string()),
        ];
        let update_condition = vec![(String::from("id"), String::from("1"))];
        update_record3(&update_table_name, &update_values, &update_condition)
            .await
            .unwrap();
    }
}

// set available_usdt
pub async fn set_available_usdt() {
    let update_table_name = String::from("asset_manage_announcement");
    // fetch current_total_usdt
    let mut asset_info = select_asset_manage_announcement().await;
    let kelly_criterion_info = select_kelly_criterion().await;

    let mut available_usdt: Decimal;
    if kelly_criterion_info.betting_rate >= 5.0 && kelly_criterion_info.betting_rate < 100.0 {
        available_usdt = decimal_mul(
            asset_info.current_total_usdt,
            rust_decimal::prelude::FromPrimitive::from_f64(
                kelly_criterion_info.betting_rate * 0.01,
            )
            .unwrap(),
        );
    } else if kelly_criterion_info.betting_rate >= 100.0 {
        available_usdt = asset_info.current_total_usdt;
    } else {
        // default: 5% of current_total_usdt
        // FIXME: dec!(0.05)
        available_usdt = decimal_mul(asset_info.current_total_usdt, dec!(1));
    }

    let unit_trade_usdt = asset_info.unit_trade_usdt;

    // set available_usdt with current_total_usdt and update is_tradable
    let update_value = {
        if available_usdt > unit_trade_usdt {
            vec![
                (String::from("available_usdt"), available_usdt.to_string()),
                (String::from("is_tradable"), (String::from("1"))),
            ]
        } else {
            vec![
                (String::from("available_usdt"), available_usdt.to_string()),
                (String::from("is_tradable"), (String::from("0"))),
            ]
        }
    };
    let update_condition = vec![(String::from("id"), String::from("1"))];
    update_record3(&update_table_name, &update_value, &update_condition)
        .await
        .unwrap();
}

// get available_usdt
pub async fn get_available_usdt() -> Decimal {
    let update_table_name = String::from("asset_manage_announcement");
    let mut asset_info = select_asset_manage_announcement().await;
    asset_info.available_usdt
}

// add available_usdt
pub async fn add_available_usdt(add_usdt: Decimal) {
    let update_table_name = String::from("asset_manage_announcement");
    let mut value_build = String::from("available_usdt + ");
    value_build.push_str(add_usdt.to_string().as_str());

    let update_values = vec![(String::from("available_usdt"), value_build)];
    let update_condition = vec![(String::from("id"), String::from("1"))];
    update_record3(&update_table_name, &update_values, &update_condition)
        .await
        .unwrap();
}

// subtract available_usdt
pub async fn sub_available_usdt(sub_usdt: Decimal) {
    let update_table_name = String::from("asset_manage_announcement");
    let mut value_build = String::from("available_usdt - ");
    value_build.push_str(sub_usdt.to_string().as_str());

    let update_values = vec![(String::from("available_usdt"), value_build)];
    let update_condition = vec![(String::from("id"), String::from("1"))];
    update_record3(&update_table_name, &update_values, &update_condition)
        .await
        .unwrap();
}

// set is_tradable as available_usdt
pub async fn set_is_tradable() {
    let update_table_name = String::from("asset_manage_announcement");
    let mut asset_info = select_asset_manage_announcement().await;
    let unit_trade_usdt = asset_info.unit_trade_usdt;
    let available_usdt = asset_info.available_usdt;

    let update_value = {
        if available_usdt > unit_trade_usdt {
            vec![(String::from("is_tradable"), (String::from("1")))]
        } else {
            vec![(String::from("is_tradable"), (String::from("0")))]
        }
    };
    let update_condition = vec![(String::from("id"), String::from("1"))];
    update_record3(&update_table_name, &update_value, &update_condition)
        .await
        .unwrap();
}

// get is_tradable
pub async fn get_is_tradable() -> bool {
    let asset_info = select_asset_manage_announcement().await;
    let is_tradable = asset_info.is_tradable;

    if asset_info.is_tradable == 1 {
        true
    } else {
        false
    }
}

pub async fn get_unit_trade_usdt() -> Decimal {
    let asset_info = select_asset_manage_announcement().await;
    asset_info.unit_trade_usdt
}

pub async fn get_current_total_usdt() -> Decimal {
    let asset_info = select_asset_manage_announcement().await;
    asset_info.current_total_usdt
}

pub async fn select_asset_manage_announcement() -> AssetInfo {
    let mut asset_info = AssetInfo::new();

    let table_name = String::from("asset_manage_announcement");
    let columns = String::from("*");
    let condition = None;

    let select_result = select_record(&table_name, &columns, &condition, &asset_info)
        .await
        .unwrap();
    let result_vec = select_result.first().unwrap();

    asset_info.initial_usdt = result_vec.initial_usdt;
    asset_info.current_total_usdt = result_vec.current_total_usdt;
    asset_info.profit = result_vec.profit;
    asset_info.realtime_expected_total_usdt = result_vec.realtime_expected_total_usdt;
    asset_info.realtime_profit = result_vec.realtime_profit;
    asset_info.available_usdt = result_vec.available_usdt;
    asset_info.is_tradable = result_vec.is_tradable;
    asset_info.unit_trade_usdt = result_vec.unit_trade_usdt;

    asset_info
}

// fetch free usdt from wallet
pub async fn fetch_free_usdt() -> Decimal {
    let wallet_info = WalletInfo::new();
    let select_table_name = {
        unsafe {
            if RUNNING_MODE == SIMUL || RUNNING_MODE == REAL {
                String::from("wallet")
            } else {
                String::from("wallet_testnet")
            }
        }
    };

    let select_columns_name = String::from("*");
    let condition = Some(String::from("WHERE asset = 'USDT'"));

    let mut select_result = select_record(
        &select_table_name,
        &select_columns_name,
        &condition,
        &wallet_info,
    )
    .await
    .unwrap();
    select_result.first().unwrap().free
}

pub async fn select_kelly_criterion() -> KellyCriterionInfo {
    let mut kelly_criterion_info = KellyCriterionInfo::new();

    let table_name = String::from("kelly_criterion");
    let columns = String::from("*");
    let condition = None;

    let select_result = select_record(&table_name, &columns, &condition, &kelly_criterion_info)
        .await
        .unwrap();
    let result_vec = select_result.first().unwrap();

    kelly_criterion_info.betting_rate = result_vec.betting_rate;
    kelly_criterion_info.win_rate = result_vec.win_rate;
    kelly_criterion_info.lose_rate = result_vec.lose_rate;
    kelly_criterion_info.profit_rate = result_vec.profit_rate;
    kelly_criterion_info.loss_rate = result_vec.loss_rate;

    kelly_criterion_info
}

pub async fn select_achievement_evaluation() -> AchievementEvaluationInfo {
    let mut achievement_evaluation_info = AchievementEvaluationInfo::new();

    let table_name = String::from("achievement_evaluation");
    let columns = String::from("*");
    let condition = None;
    // FIXME: this code should be general
    let select_result = select_record(
        &table_name,
        &columns,
        &condition,
        &achievement_evaluation_info,
    )
    .await
    .unwrap();
    let result_vec = &select_result[2]; //strategist #3

    achievement_evaluation_info.strategist = result_vec.strategist;
    achievement_evaluation_info.invested_usdt = result_vec.invested_usdt;
    achievement_evaluation_info.usdt_profit = result_vec.usdt_profit;
    achievement_evaluation_info.profit_percent = result_vec.profit_percent;

    achievement_evaluation_info
}
