// use crates
use crate::coex::assets_managing_team::*;
use crate::coex::exchange_team::*;
use crate::coin_health_check_team::request_others::TradeFee;
use crate::database_control::*;
use crate::decimal_funcs::*;
use crate::signal_association::signal_decision::*;
use crate::strategy_team::{AllData, TimeData};
use crate::value_estimation_team::datapoints::price_data::RealtimePriceData;
use crate::value_estimation_team::indicators::ema::EmaData;
use crate::value_estimation_team::indicators::sma::SmaData;
use crate::value_estimation_team::indicators::stoch_rsi::StochRsiData;
use crate::value_estimation_team::indicators::supertrend::{supertrend, SupertrendData};
use futures::future::try_join_all;
use log;
use reqwest::{Client, ClientBuilder};
use rust_decimal::{prelude::FromPrimitive, prelude::ToPrimitive, Decimal, RoundingStrategy};
use rust_decimal_macros::dec;
use serde_json::Value;
use sqlx::FromRow;
use std::collections::HashMap;
use tokio::time::*;
use super::{hmac_signature, REAL, SIMUL, TEST, RUNNING_MODE, FUTURES_URL, FUTURES_URL_TEST, API_KEY, API_KEY_TESTNET, FuturesExchangeInfo, PositionCoinList, FuturesTradeFee, EntryCoinInfo, select_listuped_positions};

pub enum TimeInForce {
    Gtc,
    Ioc,
    Fok,
}

#[derive(PartialEq)]
pub enum MarginType {
    Isolated,
    Crossed
}

pub async fn entry_position(
    price_map: &HashMap<String, f64>,
    futures_exchange_info_map: &HashMap<String, FuturesExchangeInfo>,
    future_trade_fee: &FuturesTradeFee,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let mut available_usdt = get_future_available_usdt().await?;
    let mut unit_trade_usdt = get_unit_trade_usdt().await;

    if !available_usdt.is_zero() && !unit_trade_usdt.is_zero() && available_usdt >= unit_trade_usdt && future_trade_fee.user_level.is_some() {
        let mut listup_positions = select_listuped_positions().await?;
        listup_positions.reverse();
        
        let server_epoch = get_server_epoch().await;
        let client = ClientBuilder::new()
            .timeout(tokio::time::Duration::from_millis(3000))
            .build()
            .unwrap();
        for element in &listup_positions {
            available_usdt = get_future_available_usdt().await?;
            unit_trade_usdt = get_unit_trade_usdt().await;
            if !available_usdt.is_zero() && !unit_trade_usdt.is_zero() && available_usdt >= unit_trade_usdt && futures_exchange_info_map.contains_key(&element.symbol) && price_map.get(&element.symbol).is_some() 
            && server_epoch - element.registered_server_epoch < 10_000 {
                let futures_exchange_info = futures_exchange_info_map.get(&element.symbol).unwrap();
                let lot_step_size = futures_exchange_info.stepsize;
                let tick_size = futures_exchange_info.ticksize;
                let base_asset_precision = futures_exchange_info.base_asset_precision;
                let entry_price = rust_decimal::prelude::FromPrimitive::from_f64(*price_map.get(&element.symbol).unwrap()).unwrap();
                
                // calculate minimum order usdt >= unit_trade_usdt
                let notional = futures_exchange_info.notional;
                let mut minimum_quantity = decimal_div(notional, entry_price)
                    .round_dp_with_strategy(lot_step_size.normalize().scale(), RoundingStrategy::ToPositiveInfinity);
                let mut minimum_order_usdt = decimal_mul(minimum_quantity, entry_price)
                    .round_dp_with_strategy(tick_size.normalize().scale(), RoundingStrategy::ToPositiveInfinity);
                
                if minimum_order_usdt <= unit_trade_usdt {
                    let mut base_qty_ordered = Decimal::new(0, 8);
                    base_qty_ordered = decimal_div(unit_trade_usdt, entry_price)
                        .round_dp_with_strategy(
                            lot_step_size.normalize().scale(),
                            RoundingStrategy::ToZero,
                        );

                    let mut used_usdt = Decimal::new(0, 8);
                    used_usdt = decimal_mul(base_qty_ordered, entry_price)
                        .round_dp_with_strategy(
                            tick_size.normalize().scale(),
                            RoundingStrategy::ToZero,
                        );

                    // order the symbol based on base_qty_ordered and current_price
                    limit_order_entry(
                        &element,
                        TimeInForce::Gtc,
                        entry_price,
                        base_qty_ordered,
                        used_usdt,
                        &client,
                    )
                    .await;
                }
            } else {
                break;
            }
        }
    }
    Ok(())
}

pub async fn limit_order_entry(
    entry_coin_info: &PositionCoinList,
    tif: TimeInForce,
    order_price: Decimal,
    order_quantity: Decimal,
    used_usdt: Decimal,
    client: &Client,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let update_table_name = String::from("future_ordered_coin_list");
    let server_epoch = get_server_epoch().await;
    unsafe {
        if RUNNING_MODE == SIMUL {
            let mut update_values = vec![
                (String::from("status"), String::from("SIMUL")),
                (String::from("transact_time"), server_epoch.to_string()),
                (String::from("close_time"), entry_coin_info.close_time.to_string()),
                (String::from("used_usdt"), used_usdt.to_string()),
                (String::from("entry_price"), order_price.to_string()),
                (String::from("current_price"), order_price.to_string()),
                (String::from("base_qty_ordered"), order_quantity.to_string()),
                ];

            let update_condition = vec![(String::from("id"), entry_coin_info.id.to_string())];
            update_record2(&update_table_name, &update_values, &update_condition)
                .await.unwrap();

            sub_future_available_usdt(used_usdt).await;

            println!("SIMUL positining {} {}", entry_coin_info.position, entry_coin_info.symbol);
        } else {
            // building URL and API-keys
            let mut url = String::new();
            let mut api_key = String::new();
            if RUNNING_MODE == TEST {
                url.push_str(FUTURES_URL_TEST);
                api_key = API_KEY_TESTNET.to_string();
            } else {
                url.push_str(FUTURES_URL);
                api_key = API_KEY.to_string();
            }

            let endpoint_url = "/fapi/v1/order?";
            url.push_str(endpoint_url);

            let mut url_build = String::new();

            // add parameters into URL
            url_build.push_str("&symbol=");
            url_build.push_str(&entry_coin_info.symbol);
            if entry_coin_info.position.contains("Long") {
                url_build.push_str("&side=BUY");
            } else {
                url_build.push_str("&side=SELL");
            }
            url_build.push_str("&type=LIMIT");
            url_build.push_str("&quantity=");
            url_build.push_str(order_quantity.to_string().as_str());
            url_build.push_str("&price=");
            url_build.push_str(order_price.to_string().as_str());
            match tif {
                TimeInForce::Gtc => {
                    url_build.push_str("&timeInForce=GTC");
                }
                TimeInForce::Ioc => {
                    url_build.push_str("&timeInForce=IOC");
                }
                TimeInForce::Fok => {
                    url_build.push_str("&timeInForce=FOK");
                }
            }
            
            hmac_signature(&mut url_build).await;
            url.push_str(&url_build);

            let res = client
                .post(&url)
                .header("X-MBX-APIKEY", api_key)
                .send()
                .await
                .unwrap();

            let body = res.text_with_charset("utf-8").await.unwrap();
            // deserialize JSON and then insert record into table
            let v = serde_json::from_str::<Value>(body.as_str());

            match v {
                Ok(T) => {
                    if T.get("code").is_some() { // when request failed
                    } else {
                        // when request succeed
                        let mut update_values = vec![];
                        update_values.push((String::from("order_type"), entry_coin_info.order_type.clone()));
                        update_values.push((String::from("order_id"), T.get("orderId").unwrap().as_u64().unwrap().to_string()));
                        update_values.push((String::from("transact_time"), server_epoch.to_string()));
                        update_values.push((String::from("close_time"), entry_coin_info.close_time.to_string()));

                        // status
                        if T.get("status").unwrap().as_str().unwrap() == "NEW" {
                            update_values.push((String::from("status"), String::from("NEW"))); 
                        } else if T.get("status").unwrap().as_str().unwrap() == "FILLED" {
                            update_values.push((String::from("status"), String::from("FILLED")));
                            println!("positioning {} {}", entry_coin_info.position, entry_coin_info.symbol);
                        } else if T.get("status").unwrap().as_str().unwrap() == "PARTIALLY_FILLED" {
                            update_values.push((String::from("status"), String::from("PARTIALLY_FILLED")));
                            println!("positioning {} {} (Partially filled)", entry_coin_info.position, entry_coin_info.symbol);
                        } 

                        let base_qty_ordered = rust_decimal::prelude::FromStr::from_str(
                            T.get("origQty").unwrap().as_str().unwrap(),
                        )
                        .unwrap();

                        let cummulative_quote_qty = rust_decimal::prelude::FromStr::from_str(
                            T.get("cumQuote").unwrap().as_str().unwrap(),
                        )
                        .unwrap();
                        // let base_asset_precision = exchange_info_vec
                        //     .iter()
                        //     .find(|exchange_info| exchange_info.symbol == element.symbol)
                        //     .unwrap()
                        //     .base_asset_precision;
                        let entry_price = decimal_div(cummulative_quote_qty, base_qty_ordered)
                            .round_dp_with_strategy(8, RoundingStrategy::ToZero);

                        update_values.push((String::from("used_usdt"), cummulative_quote_qty.to_string()));
                        update_values.push((String::from("entry_price"), entry_price.to_string()));
                        update_values.push((String::from("base_qty_ordered"), base_qty_ordered.to_string()));

                        // reflect available_usdt in [asset_manage_announcement]
                        // sub_available_usdt(cummulative_quote_qty).await;
                        sub_future_available_usdt(cummulative_quote_qty).await;
                        
                        let update_condition = vec![(String::from("id"), entry_coin_info.id.to_string())];
                        update_record2(&update_table_name, &update_values, &update_condition)
                            .await.unwrap();
                    }
                },
                Err(e) => {
                    log::warn!("order failed!: {}", body);
                }
            }
        }
    }
    Ok(())
}

pub async fn limit_order_close(
    entry_coin_info: &PositionCoinList,
    tif: TimeInForce,
    order_price: Decimal,
    order_quantity: Decimal,
    client: &Client,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let update_table_name = String::from("future_ordered_coin_list");
    let server_epoch = get_server_epoch().await;
    unsafe {
        if RUNNING_MODE == SIMUL {
            let mut update_values = vec![
                (String::from("order_type"), String::from("CLOSING")),
                (String::from("status"), String::from("FILLED")),
                ];

            let update_condition = vec![(String::from("id"), entry_coin_info.id.to_string())];
            update_record2(&update_table_name, &update_values, &update_condition)
                .await.unwrap();

            println!("SIMUL closed {} {}", entry_coin_info.position, entry_coin_info.symbol);

        } else {
            // building URL and API-keys
            let mut url = String::new();
            let mut api_key = String::new();
            if RUNNING_MODE == TEST {
                url.push_str(FUTURES_URL_TEST);
                api_key = API_KEY_TESTNET.to_string();
            } else {
                url.push_str(FUTURES_URL);
                api_key = API_KEY.to_string();
            }

            let endpoint_url = "/fapi/v1/order?";
            url.push_str(endpoint_url);

            let mut url_build = String::new();

            // add parameters into URL
            url_build.push_str("&symbol=");
            url_build.push_str(&entry_coin_info.symbol);
            if entry_coin_info.position.contains("Long") {
                url_build.push_str("&side=SELL");
            } else {
                url_build.push_str("&side=BUY");
            }
            url_build.push_str("&type=MARKET");
            url_build.push_str("&quantity=");
            url_build.push_str(order_quantity.to_string().as_str());
            // url_build.push_str("&price=");
            // url_build.push_str(order_price.to_string().as_str());
            // match tif {
            //     TimeInForce::Gtc => {
            //         url_build.push_str("&timeInForce=GTC");
            //     }
            //     TimeInForce::Ioc => {
            //         url_build.push_str("&timeInForce=IOC");
            //     }
            //     TimeInForce::Fok => {
            //         url_build.push_str("&timeInForce=FOK");
            //     }
            // }
            
            hmac_signature(&mut url_build).await;
            url.push_str(&url_build);

            let res = client
                .post(&url)
                .header("X-MBX-APIKEY", api_key)
                .send()
                .await
                .unwrap();

            let body = res.text_with_charset("utf-8").await.unwrap();
            // deserialize JSON and then insert record into table
            let v = serde_json::from_str::<Value>(body.as_str());

            match v {
                Ok(T) => {
                    if T.get("code").is_some() { // when request failed
                    } else {
                        // when request succeed
                        let mut update_values = vec![];
                        update_values.push((String::from("order_type"), (String::from("CLOSING"))));
                        update_values.push((String::from("order_id"), T.get("orderId").unwrap().as_u64().unwrap().to_string()));
                        update_values.push((String::from("registered_server_epoch"), server_epoch.to_string()));

                        // status
                        if T.get("status").unwrap().as_str().unwrap() == "NEW" {
                            update_values.push((String::from("status"), String::from("NEW"))); 
                        } else if T.get("status").unwrap().as_str().unwrap() == "FILLED" {
                            update_values.push((String::from("status"), String::from("FILLED")));
                            println!("Closed {} {}", entry_coin_info.position, entry_coin_info.symbol);
                        } else if T.get("status").unwrap().as_str().unwrap() == "PARTIALLY_FILLED" {
                            update_values.push((String::from("status"), String::from("PARTIALLY_FILLED")));
                            println!("Closing {} {} (Partially filled)", entry_coin_info.position, entry_coin_info.symbol);
                        } 

                        
                        let update_condition = vec![(String::from("id"), entry_coin_info.id.to_string())];
                        update_record2(&update_table_name, &update_values, &update_condition)
                            .await.unwrap();
                    }
                },
                Err(e) => {
                    log::warn!("order failed!: {}", body);
                }
            }
        }
    }
    Ok(())
}

pub async fn monitoring_unfilled_order(
    client: &Client,
    future_trade_fee: &FuturesTradeFee,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    let open_positioning_orders = select_open_positioning_orders().await;

    if !open_positioning_orders.is_empty() {
        let server_epoch = get_server_epoch().await;
        let orders_outdated = open_positioning_orders
            .iter()
            .filter(|&element| server_epoch - element.registered_server_epoch >= 60_000)
            .collect::<Vec<&PositionCoinList>>(); // wait up to 60 secs
        let orders_to_be_queried = open_positioning_orders
            .iter()
            .filter(|&element| server_epoch - element.registered_server_epoch < 60_000)
            .collect::<Vec<&PositionCoinList>>();

        // cancel orders outdated over 3mins and delete the records in [buy_ordered_coin_list]
        if !orders_outdated.is_empty() {
            for element in orders_outdated {
                cancel_open_positioning_order(element, &client, future_trade_fee).await;
                sleep(Duration::from_millis(200)).await;
            }
        }

        if !orders_to_be_queried.is_empty() {
            for element in orders_to_be_queried {
                query_open_positioning_order(element, &client, future_trade_fee).await;
                sleep(Duration::from_millis(200)).await;
            }
        }
    }

    let open_closing_orders = select_open_closing_orders().await;
    if !open_closing_orders.is_empty() {
        let server_epoch = get_server_epoch().await;
        for element in open_closing_orders {
            query_open_closing_order(&element, &client).await;
            sleep(Duration::from_millis(200)).await;
        }
        
        // let orders_outdated = open_closing_orders
        //     .iter()
        //     .filter(|&element| server_epoch - element.registered_server_epoch >= 30_000)
        //     .collect::<Vec<&PositionCoinList>>(); // wait up to 30 secs
        // let orders_to_be_queried = open_closing_orders
        //     .iter()
        //     .filter(|&element| server_epoch - element.registered_server_epoch < 30_000)
        //     .collect::<Vec<&PositionCoinList>>();

        
    }

    Ok(())
}

// Cancel an LISTUP or NEW or PARTIALLY FILLED order. (/api, Weight(IP) 1)
pub async fn cancel_open_positioning_order(
    order: &PositionCoinList,
    client: &Client,
    future_trade_fee: &FuturesTradeFee,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    if order.status.contains("LISTUP") {
        let table_name = String::from("future_ordered_coin_list");
        let mut condition_build = String::from("WHERE id = ");
        condition_build.push_str(order.id.to_string().as_str());
        condition_build.push_str(" AND symbol = \'");
        condition_build.push_str(&order.symbol);
        condition_build.push('\'');
        delete_record(&table_name, &condition_build).await;
        return Ok(());
    }
    
    // building URL and API-keys
    let mut url = String::new();
    let mut api_key = String::new();
    unsafe {
        if RUNNING_MODE == TEST {
            url.push_str(FUTURES_URL_TEST);
            api_key = API_KEY_TESTNET.to_string();
        } else if RUNNING_MODE == REAL {
            url.push_str(FUTURES_URL);
            api_key = API_KEY.to_string();
        }
    }

    let endpoint_url = "/fapi/v1/order?";
    url.push_str(endpoint_url);

    let mut url_build = String::new();
    // add parameters into URL
    url_build.push_str("&symbol=");
    url_build.push_str(&order.symbol);
    url_build.push_str("&orderId=");
    url_build.push_str(order.order_id.to_string().as_str());
    hmac_signature(&mut url_build).await;
    url.push_str(&url_build);

    let res = client
        .delete(&url)
        .header("X-MBX-APIKEY", api_key)
        .send()
        .await?;

    let body = res.text_with_charset("utf-8").await.unwrap();
    // println!("cancel_buy_order실행 body: {}", body);
    // deserialize JSON and then insert record into table
    let v = serde_json::from_str::<Value>(body.as_str());

    match v {
        Ok(T) => {
            if T.get("status").is_some() {
                // case that the order is canceled successfully
                if T.get("status").unwrap().as_str().unwrap() == "CANCELED" {
                    let table_name = String::from("future_ordered_coin_list");
                    let cummulative_quote_qty: Decimal = rust_decimal::prelude::FromStr::from_str(
                        T.get("cumQuote").unwrap().as_str().unwrap(),
                    )
                    .unwrap();

                    if cummulative_quote_qty.is_zero() {
                        // NOT Filled case
                        // delete record in [future_ordered_coin_list]
                        let mut condition_build = String::from("WHERE order_id = ");
                        condition_build.push_str(order.order_id.to_string().as_str());
                        condition_build.push_str(" AND symbol = \'");
                        condition_build.push_str(&order.symbol);
                        condition_build.push('\'');
                        delete_record(&table_name, &condition_build).await;
                    } else {
                        // Patially Filled case
                        // update values in [future_ordered_coin_list]

                        // calculate values to be updated
                        if future_trade_fee.user_level.is_some() {
                            let base_qty_ordered = rust_decimal::prelude::FromStr::from_str(
                                T.get("executedQty").unwrap().as_str().unwrap(),
                            )
                            .unwrap();

                            let entry_price = decimal_div(cummulative_quote_qty, base_qty_ordered)
                                .round_dp_with_strategy(8, RoundingStrategy::ToZero);

                            let update_values = vec![
                                (String::from("status"), String::from("FILLED")), // status
                                (String::from("used_usdt"), cummulative_quote_qty.to_string()), // used_usdt
                                (String::from("entry_price"), entry_price.to_string()), // entry_price
                                (String::from("base_qty_ordered"),base_qty_ordered.to_string()), // base_qty_ordered
                            ];
                            let update_condition = vec![
                                (String::from("order_id"), order.order_id.to_string()),
                                (String::from("symbol"), order.symbol.clone()),
                            ];
                            update_record2(&table_name, &update_values, &update_condition)
                                .await
                                .unwrap();

                            println!("partially Positioning {} {}", order.position, order.symbol);
                        }
                    }
                }
            } else if T.get("code").is_some() {
                // case that the order isn't canceled because the order completes while canceling
                // update record in ordered_coin_list
                query_open_positioning_order(order, &client, future_trade_fee).await;
            }
        }
        Err(e) => {
            query_open_positioning_order(order, &client, future_trade_fee).await;
            log::warn!("cancel order buy failed!: {}", body);
        }
    }

    Ok(())
}

// query order and update record in buy_ordered_coin_list (/api, Weight(IP) 1)
pub async fn query_open_positioning_order(
    order: &PositionCoinList,
    client: &Client,
    future_trade_fee: &FuturesTradeFee,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // building URL and API-keys
    let mut url = String::new();
    let mut api_key = String::new();
    unsafe {
        if RUNNING_MODE == TEST {
            url.push_str(FUTURES_URL_TEST);
            api_key = API_KEY_TESTNET.to_string();
        } else if RUNNING_MODE == REAL {
            url.push_str(FUTURES_URL);
            api_key = API_KEY.to_string();
        }
    }

    let endpoint_url = "/fapi/v1/order?";
    url.push_str(endpoint_url);

    let mut url_build = String::new();

    // add parameters into URL
    url_build.push_str("&symbol=");
    url_build.push_str(&order.symbol);
    url_build.push_str("&orderId=");
    url_build.push_str(order.order_id.to_string().as_str());

    hmac_signature(&mut url_build).await;
    url.push_str(&url_build);

    let res = client
        .get(&url)
        .header("X-MBX-APIKEY", api_key)
        .send()
        .await?;

    let body = res.text_with_charset("utf-8").await.unwrap();
    // println!("query_buy_order: {}", body);
    // deserialize JSON and then update record in table
    let v = serde_json::from_str::<Value>(body.as_str());

    match v {
        Ok(T) => {
            if T.get("status").is_some() {
                if T.get("status").unwrap().as_str().unwrap() == "FILLED"
                    || T.get("status").unwrap().as_str().unwrap() == "PARTIALLY_FILLED"
                {
                    // update values in [future_ordered_coin_list]
                    let table_name = String::from("future_ordered_coin_list");
                    let mut value_build = String::new();
                    value_build.push_str(T.get("status").unwrap().as_str().unwrap());

                    // calculate values to be updated
                    let base_qty_ordered = rust_decimal::prelude::FromStr::from_str(
                        T.get("executedQty").unwrap().as_str().unwrap(),
                    )
                    .unwrap();

                    let cummulative_quote_qty = rust_decimal::prelude::FromStr::from_str(
                        T.get("cumQuote").unwrap().as_str().unwrap(),
                    )
                    .unwrap();

                    let entry_price = decimal_div(cummulative_quote_qty, base_qty_ordered)
                        .round_dp_with_strategy(8, RoundingStrategy::ToZero);

                    let update_values = vec![
                        (String::from("status"), value_build), // status
                        (String::from("used_usdt"), cummulative_quote_qty.to_string()), // used_usdt
                        (String::from("entry_price"), entry_price.to_string()), // entry_price
                        (String::from("base_qty_ordered"), base_qty_ordered.to_string()), // base_qty_ordered
                    ];
                    let update_condition = vec![
                        (String::from("order_id"), order.order_id.to_string()),
                        (String::from("symbol"), order.symbol.clone()),
                    ];
                    update_record2(&table_name, &update_values, &update_condition)
                        .await
                        .unwrap();

                    if T.get("status").unwrap().as_str().unwrap() == "FILLED" {
                        println!("Positioning {} {}", order.position, order.symbol);
                    }
                    
                } else if T.get("status").unwrap().as_str().unwrap() == "CANCELED" {
                    let update_table_name = String::from("future_ordered_coin_list");
                    let update_values = vec![(String::from("status"), String::from("CANCELED"))];
                    let update_condition = vec![
                            (String::from("order_id"), order.order_id.to_string()),
                            (String::from("symbol"), order.symbol.clone()),
                        ];
                    update_record2(&update_table_name, &update_values, &update_condition)
                        .await
                        .unwrap();
                }
            }
        }
        Err(e) => {
            log::warn!("query order failed!: {}", body);
        }
    }
    Ok(())
}

pub async fn query_open_closing_order(
    order: &PositionCoinList,
    client: &Client,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // building URL and API-keys
    let mut url = String::new();
    let mut api_key = String::new();
    unsafe {
        if RUNNING_MODE == TEST {
            url.push_str(FUTURES_URL_TEST);
            api_key = API_KEY_TESTNET.to_string();
        } else if RUNNING_MODE == REAL {
            url.push_str(FUTURES_URL);
            api_key = API_KEY.to_string();
        }
    }

    let endpoint_url = "/fapi/v1/order?";
    url.push_str(endpoint_url);

    let mut url_build = String::new();

    // add parameters into URL
    url_build.push_str("&symbol=");
    url_build.push_str(&order.symbol);
    url_build.push_str("&orderId=");
    url_build.push_str(order.order_id.to_string().as_str());

    hmac_signature(&mut url_build).await;
    url.push_str(&url_build);

    let res = client
        .get(&url)
        .header("X-MBX-APIKEY", api_key)
        .send()
        .await?;

    let body = res.text_with_charset("utf-8").await.unwrap();
    
    // deserialize JSON and then update record in table
    let v = serde_json::from_str::<Value>(body.as_str());

    match v {
        Ok(T) => {
            if T.get("status")
                .is_some_and(|a| a.as_str().unwrap() == "FILLED")
                // || T.get("status")
                //     .is_some_and(|a| a.as_str().unwrap() == "PARTIALLY_FILLED")
            {
                // update values in [future_ordered_coin_list]
                let table_name = String::from("future_ordered_coin_list");
                let mut value_build = String::new();
                value_build.push_str(T.get("status").unwrap().as_str().unwrap());

                // calculate values to be updated


                let usdt_size = rust_decimal::prelude::FromStr::from_str(
                    T.get("cumQuote").unwrap().as_str().unwrap(),
                )
                .unwrap();

                let mut pnl = Decimal::new(0, 8);
                if order.position.contains("Long") {
                    pnl = decimal_sub(usdt_size, order.used_usdt);
                } else {
                    pnl = decimal_sub(order.used_usdt, usdt_size);
                }
                let expected_get_usdt = decimal_add(order.used_usdt, pnl);

                let update_values = vec![
                    (String::from("status"), value_build), // status
                    (String::from("pnl"), pnl.to_string()), // pnl
                    (String::from("expected_get_usdt"), expected_get_usdt.to_string()), // base_qty_ordered
                ];
                let update_condition = vec![
                    (String::from("order_id"), order.order_id.to_string()),
                    (String::from("symbol"), order.symbol.clone()),
                ];
                update_record2(&table_name, &update_values, &update_condition)
                    .await
                    .unwrap();

               
                println!("closed {} {}", order.position, order.symbol);
                
            }
        }
        Err(e) => {
            log::warn!("query sell order failed!: {}", body);
        }
    }
    Ok(())
}

// 
pub async fn set_initial_leverage(symbol: &String, leverage_ratio: i32, client: &Client) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // building URL and API-keys
    let mut url = String::new();
    let mut api_key = String::new();
    unsafe {
        if RUNNING_MODE == TEST {
            url.push_str(FUTURES_URL_TEST);
            api_key = API_KEY_TESTNET.to_string();
        } else {
            url.push_str(FUTURES_URL);
            api_key = API_KEY.to_string();
        }
    }

    let endpoint_url = "/fapi/v1/leverage?";
    url.push_str(endpoint_url);

    let mut url_build = String::new();
    url_build.push_str("&symbol=");
    url_build.push_str(symbol);
    url_build.push_str("&leverage=");
    url_build.push_str(leverage_ratio.to_string().as_str());
    hmac_signature(&mut url_build).await;
    url.push_str(&url_build);
    
    let res = client
        .post(&url)
        .header("X-MBX-APIKEY", api_key)
        .send()
        .await?;

    let body = res.text_with_charset("utf-8").await?;
    
    // deserialize JSON
    let v = serde_json::from_str::<Value>(body.as_str());
    match v {
        Ok(T) => {
            if let (Some(leverage), Some(symbol), Some(max_notional_value)) = (T.get("leverage"), T.get("symbol"), T.get("maxNotionalValue")) {
                log::info!("symbol: {:?}. leverage: {:?}, max_notional_value: {:?}", symbol, leverage, max_notional_value);
            } else {
                log::error!("Endpoint(/fapi/v1/leverage?) output changed!");
                return Err("Err")?;
            }
        }
        Err(e) => {
            log::warn!("account information failed!: {}, {}", body, e);
        }
    }
    Ok(())
}

pub async fn set_margin_type(symbol: &String, margin_type: MarginType, client: &Client) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // building URL and API-keys
    let mut url = String::new();
    let mut api_key = String::new();
    unsafe {
        if RUNNING_MODE == TEST {
            url.push_str(FUTURES_URL_TEST);
            api_key = API_KEY_TESTNET.to_string();
        } else {
            url.push_str(FUTURES_URL);
            api_key = API_KEY.to_string();
        }
    }

    let endpoint_url = "/fapi/v1/marginType?";
    url.push_str(endpoint_url);

    let mut url_build = String::new();
    url_build.push_str("&symbol=");
    url_build.push_str(symbol);
    url_build.push_str("&marginType=");
    if margin_type == MarginType::Isolated {
        url_build.push_str("ISOLATED");
    } else {
        url_build.push_str("CROSSED");
    }
    hmac_signature(&mut url_build).await;
    url.push_str(&url_build);
    
    let res = client
        .post(&url)
        .header("X-MBX-APIKEY", api_key)
        .send()
        .await?;

    let body = res.text_with_charset("utf-8").await?;
    
    // deserialize JSON
    let v = serde_json::from_str::<Value>(body.as_str());
    match v {
        Ok(T) => {
            if let (Some(code), Some(msg)) = (T.get("code"), T.get("msg")) {
                log::info!("code: {:?}. msg: {:?}", code, msg);
            } else {
                log::error!("Endpoint(/fapi/v1/marginType?) output changed!");
                return Err("Err")?;
            }
        }
        Err(e) => {
            log::warn!("account information failed!: {}, {}", body, e);
        }
    }
    Ok(())
}

// Basic: One-way Mode
pub async fn set_position_mode(client: &Client) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // building URL and API-keys
    let mut url = String::new();
    let mut api_key = String::new();
    unsafe {
        if RUNNING_MODE == TEST {
            url.push_str(FUTURES_URL_TEST);
            api_key = API_KEY_TESTNET.to_string();
        } else {
            url.push_str(FUTURES_URL);
            api_key = API_KEY.to_string();
        }
    }

    let endpoint_url = "/fapi/v1/positionSide/dual?";
    url.push_str(endpoint_url);

    let mut url_build = String::new();
    url_build.push_str("&dualSidePosition=");
    url_build.push_str("false"); // true: Hedge Mode, false: One-way Mode
    hmac_signature(&mut url_build).await;
    url.push_str(&url_build);
    
    let res = client
        .post(&url)
        .header("X-MBX-APIKEY", api_key)
        .send()
        .await?;

    let body = res.text_with_charset("utf-8").await?;
    
    // deserialize JSON
    let v = serde_json::from_str::<Value>(body.as_str());
    match v {
        Ok(T) => {
            if let (Some(code), Some(msg)) = (T.get("code"), T.get("msg")) {
                log::info!("code: {:?}. msg: {:?}", code, msg);
            } else {
                log::error!("Endpoint(/fapi/v1/marginType?) output changed!");
                return Err("Err")?;
            }
        }
        Err(e) => {
            log::warn!("account information failed!: {}, {}", body, e);
        }
    }
    Ok(())
}

// Basic: Single-Asset Mode
pub async fn set_asset_mode(client: &Client) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // building URL and API-keys
    let mut url = String::new();
    let mut api_key = String::new();
    unsafe {
        if RUNNING_MODE == TEST {
            url.push_str(FUTURES_URL_TEST);
            api_key = API_KEY_TESTNET.to_string();
        } else {
            url.push_str(FUTURES_URL);
            api_key = API_KEY.to_string();
        }
    }

    let endpoint_url = "/fapi/v1/multiAssetsMargin?";
    url.push_str(endpoint_url);

    let mut url_build = String::new();
    url_build.push_str("&multiAssetsMargin=");
    url_build.push_str("false"); // true: Multi-Assets Mode, false: Single-Asset Mode
    hmac_signature(&mut url_build).await;
    url.push_str(&url_build);
    
    let res = client
        .post(&url)
        .header("X-MBX-APIKEY", api_key)
        .send()
        .await?;

    let body = res.text_with_charset("utf-8").await?;
    
    // deserialize JSON
    let v = serde_json::from_str::<Value>(body.as_str());
    match v {
        Ok(T) => {
            if let (Some(code), Some(msg)) = (T.get("code"), T.get("msg")) {
                log::info!("code: {:?}. msg: {:?}", code, msg);
            } else {
                log::error!("Endpoint(/fapi/v1/marginType?) output changed!");
                return Err("Err")?;
            }
        }
        Err(e) => {
            log::warn!("account information failed!: {}, {}", body, e);
        }
    }
    Ok(())
}

pub async fn get_last_price(client: &Client, price_map: &mut HashMap<String, f64>,) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // building URL and API-keys
    let mut url = String::new();
    let mut api_key = String::new();
    unsafe {
        if RUNNING_MODE == TEST {
            url.push_str(FUTURES_URL_TEST);
            api_key = API_KEY_TESTNET.to_string();
        } else {
            url.push_str(FUTURES_URL);
            api_key = API_KEY.to_string();
        }
    }

    let endpoint_url = "/fapi/v2/ticker/price";
    url.push_str(endpoint_url);
    
    let res = client
        .get(&url)
        .header("X-MBX-APIKEY", api_key)
        .send()
        .await?;

    let body = res.text_with_charset("utf-8").await?;
    
    // deserialize JSON
    let v: Value = serde_json::from_str(body.as_str())?;
    let into_vec = v.as_array();
    if into_vec == None {
        return Err("Err")?;
    }
    let mut object_map = &serde_json::map::Map::new();

    let mut symbol = String::new();
    let mut price = 0.0;
    for element in into_vec.unwrap() {
        object_map = element.as_object().unwrap();
        let mut object_map_iter = object_map.iter();

        for element in object_map_iter {
            match element.0.as_str() {
                "symbol" => symbol = element.1.as_str().unwrap().to_string(),
                "price" => price = element.1.as_str().unwrap().parse::<f64>().unwrap(),
                "time" => {},
                _ => {
                    log::error!("Elements in body msg are changed. Please update both your coinprices table and vectors.");
                }
            }
            price_map.insert(symbol.clone(), price);
        }
    }
    Ok(())
}

// request exchange information. (/api, Weight(IP) 1)
pub async fn request_future_exchange_infomation(
    client: &Client,
    exchange_info_map: &mut HashMap<String, FuturesExchangeInfo>,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    // building URL and API-keys
    let mut url = String::new();

    url.push_str("https://fapi.binance.com");

    let endpoint_url = "/fapi/v1/exchangeInfo";
    url.push_str(endpoint_url);

    let mut res = client.get(&url).send().await?;

    while !res.status().is_success() {
        sleep(Duration::from_secs(5)).await;
        res = client.get(&url).send().await?;
    }

    let body = res.text_with_charset("utf-8").await?;

    // deserialize JSON
    let v: Value = serde_json::from_str(body.as_str())?;
    let mut into_vec = v.as_object();
    if into_vec == None {
        return Err("Err")?;
    }

    let mut symbol = String::new();
    let mut exchange_info = FuturesExchangeInfo::new();
    let mut data_map_temp: HashMap<String, FuturesExchangeInfo> = HashMap::new();

    for element in into_vec.unwrap() {
        if element.0.contains("symbols") {
            for element in element.1.as_array().unwrap() {
                if element.is_object() {
                    if element
                        .get("symbol")
                        .unwrap()
                        .as_str()
                        .unwrap()
                        .ends_with("USDT")
                    {
                        if element.get("contractType").is_some_and(|a| a.as_str().is_some_and(|b| b == "PERPETUAL")) {
                            symbol = (element.get("symbol").unwrap().as_str().unwrap().to_string());
                            exchange_info.base_asset_precision =
                                (element.get("baseAssetPrecision").unwrap().as_u64().unwrap()) as u32;
                            exchange_info.quote_precision =
                                (element.get("quotePrecision").unwrap().as_u64().unwrap()) as u32;    
                            for element in element.get("filters").unwrap().as_array().unwrap() {
                                if element
                                    .as_object()
                                    .unwrap()
                                    .get("filterType")
                                    .unwrap()
                                    .as_str()
                                    .unwrap()
                                    .starts_with("LOT_SIZE")
                                {
                                    exchange_info.stepsize = rust_decimal::prelude::FromStr::from_str(
                                        element.get("stepSize").unwrap().as_str().unwrap(),
                                    )
                                    .unwrap();
                                } else if element
                                    .as_object()
                                    .unwrap()
                                    .get("filterType")
                                    .unwrap()
                                    .as_str()
                                    .unwrap()
                                    .starts_with("PRICE_FILTER")
                                {
                                    exchange_info.ticksize = rust_decimal::prelude::FromStr::from_str(
                                        element.get("tickSize").unwrap().as_str().unwrap(),
                                    )
                                    .unwrap();
                                } else if element
                                    .as_object()
                                    .unwrap()
                                    .get("filterType")
                                    .unwrap()
                                    .as_str()
                                    .unwrap()
                                    .starts_with("MIN_NOTIONAL")
                                {
                                    exchange_info.notional = rust_decimal::prelude::FromStr::from_str(
                                        element.get("notional").unwrap().as_str().unwrap(),
                                    )
                                    .unwrap();
                                }
                            }
                        data_map_temp.insert(symbol.clone(), exchange_info.clone());
                        }
                    }
                }
            }
        }
    }

    *exchange_info_map = data_map_temp;
    
    Ok(())
}

// subtract available USDT
pub async fn sub_future_available_usdt(sub_usdt: Decimal) {
    let update_table_name = String::from("future_available_balance");
    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();
}

// get available USDT
pub async fn get_future_available_usdt() -> Result<Decimal, Box<dyn std::error::Error + Send + Sync>> {
    let select_table_name = String::from("future_available_balance");
    let select_columns = String::from("*");
    let select_condition = None;

    #[derive(FromRow)]
    struct AvailableUsdt {
        available_usdt: Decimal,
    }
    let data_struct = AvailableUsdt{ available_usdt: Decimal::new(0, 8) };

    let select_result = try_select_record(
        &select_table_name,
        &select_columns,
        &select_condition,
        &data_struct,
    )
    .await;

    if select_result.is_ok() {
        Ok(select_result.unwrap().first().unwrap().available_usdt)
    } else {
        eprint!("get_future_available_usdt() error!");
        Err("error")?
    }
}

async fn select_open_positioning_orders() -> Vec<PositionCoinList> {
    let select_table_name = String::from("future_ordered_coin_list");
    let select_columns = String::from("*");
    let select_condition = Some(String::from(
        "WHERE order_type = 'POSITIONING' AND (status = 'LISTUP' OR status = 'NEW' OR status = 'PARTIALLY_FILLED')",
    ));
    let data_struct = PositionCoinList::new();
    let select_result = try_select_record(
        &select_table_name,
        &select_columns,
        &select_condition,
        &data_struct,
    )
    .await
    .unwrap();

    select_result
}

async fn select_open_closing_orders() -> Vec<PositionCoinList> {
    let select_table_name = String::from("future_ordered_coin_list");
    let select_columns = String::from("*");
    let select_condition = Some(String::from(
        "WHERE order_type = 'CLOSING' AND (status = 'NEW' OR status = 'PARTIALLY_FILLED')",
    ));
    let data_struct = PositionCoinList::new();
    let select_result = try_select_record(
        &select_table_name,
        &select_columns,
        &select_condition,
        &data_struct,
    )
    .await
    .unwrap();

    select_result
}
