"""Oanda Model."""
__docformat__ = "numpy"

import json
import logging
from datetime import datetime
from typing import Dict, Union

import pandas as pd
from oandapyV20 import API
from oandapyV20.endpoints import (
    accounts,
    forexlabs,
    instruments,
    orders,
    positions,
    pricing,
    trades,
)
from oandapyV20.exceptions import V20Error

from openbb_terminal import config_terminal as cfg
from openbb_terminal.decorators import log_start_end
from openbb_terminal.rich_config import console

logger = logging.getLogger(__name__)

if cfg.OANDA_ACCOUNT_TYPE != "REPLACE_ME":
    client = API(access_token=cfg.OANDA_TOKEN, environment=cfg.OANDA_ACCOUNT_TYPE)
else:
    client = None
account = cfg.OANDA_ACCOUNT


@log_start_end(log=logger)
def fx_price_request(
    accountID: str = account, instrument: Union[str, None] = None
) -> Union[Dict[str, str], bool]:
    """Request price for a forex pair.

    Parameters
    ----------
    accountID : str, optional
        Oanda account ID, by default cfg.OANDA_ACCOUNT
    instrument : Union[str, None]
        The loaded currency pair, by default None

    Returns
    -------
    Union[Dict[str, str], bool]
        The currency pair price or False
    """
    if accountID == "REPLACE_ME":
        console.print("Error: Oanda account credentials are required.")
        return False
    if instrument is None:
        console.print(
            "Error: An instrument should be loaded before running this command."
        )
        return False
    try:
        parameters = {"instruments": instrument}
        request = pricing.PricingInfo(accountID=accountID, params=parameters)
        response = client.request(request)
        return response
    except V20Error as e:
        logger.exception(str(e))
        d_error = json.loads(e.msg)
        console.print(d_error["errorMessage"], "\n")
        return False


@log_start_end(log=logger)
def account_summary_request(accountID: str = account) -> Union[pd.DataFrame, bool]:
    """Request Oanda account summary.

    Parameters
    ----------
    accountID : str, optional
        Oanda account ID, by default cfg.OANDA_ACCOUNT

    Returns
    -------
    Union[pd.DataFrame, bool]
        Account summary data or False
    """
    if accountID == "REPLACE_ME":
        console.print("Error: Oanda account credentials are required.")
        return False
    if client is None:
        return False

    try:
        request = accounts.AccountSummary(accountID=accountID)
        response = client.request(request)
        df_summary = pd.DataFrame(
            [
                {"Type": "Balance", "Value": response["account"]["balance"]},
                {"Type": "NAV", "Value": response["account"]["NAV"]},
                {
                    "Type": "Unrealized P/L",
                    "Value": response["account"]["unrealizedPL"],
                },
                {"Type": "Total P/L", "Value": response["account"]["pl"]},
                {
                    "Type": "Open Trade Count",
                    "Value": response["account"]["openTradeCount"],
                },
                {
                    "Type": "Margin Available",
                    "Value": response["account"]["marginAvailable"],
                },
                {"Type": "Margin Used", "Value": response["account"]["marginUsed"]},
                {
                    "Type": "Margin Closeout",
                    "Value": response["account"]["marginCloseoutNAV"],
                },
                {
                    "Type": "Margin Closeout Percent",
                    "Value": response["account"]["marginCloseoutPercent"],
                },
                {
                    "Type": "Margin Closeout Position Value",
                    "Value": response["account"]["marginCloseoutPositionValue"],
                },
            ]
        )
        return df_summary
    except V20Error as e:
        logger.exception(str(e))
        d_error = json.loads(e.msg)
        console.print(d_error["errorMessage"], "\n")
        return False


@log_start_end(log=logger)
def orderbook_plot_data_request(
    instrument: Union[str, None] = None, accountID: str = account
) -> Union[pd.DataFrame, bool]:
    """Request order book data for plotting.

    Parameters
    ----------
    instrument : Union[str, None]
        The loaded currency pair, by default None
    accountID : str, optional
        Oanda account ID, by default cfg.OANDA_ACCOUNT

    Returns
    -------
    Union[pd.DataFrame, bool]
        Order book data or False
    """
    if accountID == "REPLACE_ME":
        console.print("Error: Oanda account credentials are required.")
        return False
    if instrument is None:
        console.print(
            "Error: An instrument should be loaded before running this command."
        )
        return False
    parameters = {"bucketWidth": "1"}

    if client is None:
        return False

    try:
        request = instruments.InstrumentsOrderBook(
            instrument=instrument, params=parameters
        )
        response = client.request(request)
        df_orderbook_data = pd.DataFrame.from_dict(response["orderBook"]["buckets"])
        return df_orderbook_data
    except V20Error as e:
        logger.exception(str(e))
        d_error = json.loads(e.msg)
        console.print(d_error["errorMessage"], "\n")
        return False


@log_start_end(log=logger)
def positionbook_plot_data_request(
    instrument: Union[str, None] = None, accountID: str = account
) -> Union[pd.DataFrame, bool]:
    """Request position book data for plotting.

    Parameters
    ----------
    instrument : Union[str, None]
        The loaded currency pair, by default None
    accountID : str, optional
        Oanda account ID, by default cfg.OANDA_ACCOUNT

    Returns
    -------
    Union[pd.DataFrame, bool]
        Position book data or False
    """
    if accountID == "REPLACE_ME":
        console.print("Error: Oanda account credentials are required.")
        return False
    if instrument is None:
        console.print(
            "Error: An instrument should be loaded before running this command."
        )
        return False
    if client is None:
        return False

    try:
        request = instruments.InstrumentsPositionBook(instrument=instrument)
        response = client.request(request)
        df_positionbook_data = pd.DataFrame.from_dict(
            response["positionBook"]["buckets"]
        )
        return df_positionbook_data
    except V20Error as e:
        logger.exception(str(e))
        d_error = json.loads(e.msg)
        console.print(d_error["errorMessage"], "\n")
        return False


@log_start_end(log=logger)
def order_history_request(
    order_state: str = "PENDING", order_count: int = 0, accountID: str = account
) -> Union[pd.DataFrame, bool]:
    """Request the orders list from Oanda.

    Parameters
    ----------
    order_state : str
        Filter orders by a specific state ("PENDING", "CANCELLED", etc.)
    order_count : int
        Limit the number of orders to retrieve
    accountID : str, optional
        Oanda account ID, by default cfg.OANDA_ACCOUNT
    """
    if accountID == "REPLACE_ME":
        console.print("Error: Oanda account credentials are required.")
        return False
    parameters: Dict[str, Union[str, int]] = {}
    parameters["state"] = order_state
    parameters["count"] = order_count

    if client is None:
        return False

    try:
        request = orders.OrderList(accountID, parameters)
        response = client.request(request)

        df_order_list = pd.DataFrame.from_dict(response["orders"])
        df_order_list = df_order_list[
            ["id", "instrument", "units", "price", "state", "type"]
        ]
        return df_order_list
    except KeyError:
        logger.exception("No orders were found")
        console.print("No orders were found\n")
        return False
    except V20Error as e:
        logger.exception(str(e))
        d_error = json.loads(e.msg)
        console.print(d_error["errorMessage"], "\n")
        return False


@log_start_end(log=logger)
def create_order_request(
    price: int = 0,
    units: int = 0,
    instrument: Union[str, None] = None,
    accountID: str = account,
) -> Union[pd.DataFrame, bool]:
    """Request creation of buy/sell trade order.

    Parameters
    ----------
    instrument : Union[str, None]
        The loaded currency pair, by default None
    price : int
        The price to set for the limit order.
    units : int
        The number of units to place in the order request.
    accountID : str, optional
        Oanda account ID, by default cfg.OANDA_ACCOUNT

    Returns
    -------
    Union[pd.DataFrame, bool]
        Orders data or False
    """
    if accountID == "REPLACE_ME":
        console.print("Error: Oanda account credentials are required.")
        return False
    if instrument is None:
        console.print(
            "Error: An instrument should be loaded before running this command."
        )
        return False
    if "JPY" in instrument or "THB" in instrument or "HUF" in instrument:
        price = round(price, 3)
    else:
        price = round(price, 5)
    data = {
        "order": {
            "price": price,
            "instrument": instrument,
            "units": units,
            "type": "LIMIT",
            "timeInForce": "GTC",
            "positionFill": "DEFAULT",
        }
    }

    if client is None:
        return False

    try:
        request = orders.OrderCreate(accountID, data)
        response = client.request(request)
        order_data = []
        order_data.append(
            {
                "Order ID": response["orderCreateTransaction"]["id"],
                "Instrument": response["orderCreateTransaction"]["instrument"],
                "Price": response["orderCreateTransaction"]["price"],
                "Units": response["orderCreateTransaction"]["units"],
            }
        )
        df_orders = pd.DataFrame.from_dict(order_data)
        return df_orders
    except V20Error as e:
        logger.exception(str(e))
        d_error = json.loads(e.msg)
        console.print(d_error["errorMessage"], "\n")
        return False
    except Exception as e:
        logger.exception(str(e))
        console.print(e)
        return False


@log_start_end(log=logger)
def cancel_pending_order_request(
    orderID: str, accountID: str = account
) -> Union[str, bool]:
    """Request cancellation of a pending order.

    Parameters
    ----------
    orderID : str
        The pending order ID to cancel.
    accountID : str, optional
        Oanda account ID, by default cfg.OANDA_ACCOUNT
    """
    if accountID == "REPLACE_ME":
        console.print("Error: Oanda account credentials are required.")
        return False

    if client is None:
        return False

    try:
        request = orders.OrderCancel(accountID, orderID)
        response = client.request(request)
        order_id = response["orderCancelTransaction"]["orderID"]
        return order_id
    except V20Error as e:
        logger.exception(str(e))
        d_error = json.loads(e.msg)
        console.print(d_error["errorMessage"], "\n")
        return False


@log_start_end(log=logger)
def open_positions_request(accountID: str = account) -> Union[pd.DataFrame, bool]:
    """Request information on open positions.

    Parameters
    ----------
    accountID : str, optional
        Oanda account ID, by default cfg.OANDA_ACCOUNT
    """
    if accountID == "REPLACE_ME":
        console.print("Error: Oanda account credentials are required.")
        return False

    if client is None:
        return False

    try:
        request = positions.OpenPositions(accountID)
        response = client.request(request)
        position_data = [
            {
                "Instrument": response["positions"][i]["instrument"],
                "Long Units": response["positions"][i]["long"]["units"],
                "Total Long P/L": response["positions"][i]["long"]["units"],
                "Unrealized Long P/L": response["positions"][i]["long"]["unrealizedPL"],
                "Short Units": response["positions"][i]["short"]["units"],
                "Total Short P/L": response["positions"][i]["short"]["pl"],
                "Short Unrealized P/L": response["positions"][i]["short"][
                    "unrealizedPL"
                ],
            }
            for i in range(len(response["positions"]))
        ]

        df_positions = pd.DataFrame.from_dict(position_data)
        return df_positions
    except V20Error as e:
        logger.exception(str(e))
        d_error = json.loads(e.msg)
        console.print(d_error["errorMessage"], "\n")
        return False


@log_start_end(log=logger)
def pending_orders_request(accountID: str = account) -> Union[pd.DataFrame, bool]:
    """Request information on pending orders.

    Parameters
    ----------
    accountID : str, optional
        Oanda account ID, by default cfg.OANDA_ACCOUNT

    Returns
    -------
    Union[pd.DataFrame, bool]
        Pending orders data or False
    """
    if accountID == "REPLACE_ME":
        console.print("Error: Oanda account credentials are required.")
        return False

    if client is None:
        return False

    try:
        request = orders.OrdersPending(accountID)
        response = client.request(request)
        pending_data = [
            {
                "Order ID": response["orders"][i]["id"],
                "Instrument": response["orders"][i]["instrument"],
                "Price": response["orders"][i]["price"],
                "Units": response["orders"][i]["units"],
                "Time Created": response["orders"][i]["createTime"][:10]
                + " "
                + response["orders"][i]["createTime"][11:19],
                "Time In Force": response["orders"][i]["timeInForce"],
            }
            for i in range(len(response["orders"]))
        ]

        if len(pending_data) == 0:
            return pd.DataFrame()
        df_pending = pd.DataFrame.from_dict(pending_data)
        return df_pending
    except V20Error as e:
        logger.exception(str(e))
        d_error = json.loads(e.msg)
        console.print(d_error["errorMessage"], "\n")
        return False


@log_start_end(log=logger)
def open_trades_request(accountID: str = account) -> Union[pd.DataFrame, bool]:
    """Request open trades data.

    Parameters
    ----------
    accountID : str, optional
        Oanda account ID, by default cfg.OANDA_ACCOUNT

    Returns
    -------
    Union[pd.DataFrame, bool]
        Open trades data or False
    """
    if accountID == "REPLACE_ME":
        console.print("Error: Oanda account credentials are required.")
        return False

    if client is None:
        return False

    try:
        request = trades.OpenTrades(accountID)
        response = client.request(request)
        if "trades" in response and len(response["trades"]) > 0:
            df_trades = pd.DataFrame.from_dict(response["trades"])
            df_trades = df_trades[
                [
                    "id",
                    "instrument",
                    "initialUnits",
                    "currentUnits",
                    "price",
                    "unrealizedPL",
                ]
            ]
            df_trades = df_trades.rename(
                columns={
                    "id": "ID",
                    "instrument": "Instrument",
                    "initialUnits": "Initial Units",
                    "currentUnits": "Current Units",
                    "price": "Entry Price",
                    "unrealizedPL": "Unrealized P/L",
                }
            )
        else:
            df_trades = pd.DataFrame()
        return df_trades
    except V20Error as e:
        logger.exception(str(e))
        d_error = json.loads(e.msg)
        console.print(d_error["errorMessage"], "\n")
        return False


@log_start_end(log=logger)
def close_trades_request(
    orderID: str, units: Union[int, None] = 0, accountID: str = account
) -> Union[pd.DataFrame, bool]:
    """Close a trade.

    Parameters
    ----------
    orderID : str
        ID of the order to close
    units : Union[int, None]
        Number of units to close. If empty default to all.
    accountID : str, optional
        Oanda account ID, by default cfg.OANDA_ACCOUNT

    Returns
    -------
    Union[pd.DataFrame, bool]
        Close trades data or False
    """
    if accountID == "REPLACE_ME":
        console.print("Error: Oanda account credentials are required.")
        return False
    data = {}
    if units is not None:
        data["units"] = units

    if client is None:
        return False

    try:
        request = trades.TradeClose(accountID, orderID, data)
        response = client.request(request)

        close_data = []
        close_data.append(
            {
                "OrderID": response["orderCreateTransaction"]["tradeClose"]["tradeID"],
                "Instrument": response["orderFillTransaction"]["instrument"],
                "Units": response["orderCreateTransaction"]["units"],
                "Price": response["orderFillTransaction"]["price"],
                "P/L": response["orderFillTransaction"]["pl"],
            }
        )
        if len(close_data) == 0:
            return pd.DataFrame()
        df_trades = pd.DataFrame.from_dict(close_data)
        return df_trades
    except V20Error as e:
        logger.exception(str(e))
        d_error = json.loads(e.msg)
        console.print(d_error["errorMessage"], "\n")
        return False


@log_start_end(log=logger)
def get_candles_dataframe(
    instrument: Union[str, None] = None, granularity: str = "D", candlecount: int = 180
) -> Union[pd.DataFrame, bool]:
    """Request data for candle chart.

    Parameters
    ----------
    instrument : str
        Loaded currency pair code
    granularity : str, optional
        Data granularity, by default "D"
    candlecount : int, optional
        Limit for the number of data points, by default 180

    Returns
    -------
    Union[pd.DataFrame, bool]
        Candle chart data or False
    """
    if instrument is None:
        console.print(
            "Error: An instrument should be loaded before running this command."
        )
        return False
    parameters = {
        "granularity": granularity,
        "count": candlecount,
    }

    if client is None:
        return False

    try:
        request = instruments.InstrumentsCandles(instrument, params=parameters)
        response = client.request(request)
        candles_data = [
            {
                "Date": response["candles"][i]["time"][:10]
                + " "
                + response["candles"][i]["time"][11:19],
                "Open": float(response["candles"][i]["mid"]["o"]),
                "High": float(response["candles"][i]["mid"]["h"]),
                "Low": float(response["candles"][i]["mid"]["l"]),
                "Close": float(response["candles"][i]["mid"]["c"]),
                "Volume": response["candles"][i]["volume"],
            }
            for i in range(len(response["candles"]))
        ]

        if len(candles_data) == 0:
            df_candles = pd.DataFrame()
        else:
            df_candles = pd.DataFrame(candles_data)
            df_candles.set_index("Date", inplace=True)
            df_candles.index = pd.to_datetime(df_candles.index)
        return df_candles
    except V20Error as e:
        logger.exception(str(e))
        d_error = json.loads(e.msg)
        console.print(d_error["errorMessage"], "\n")
        return False


@log_start_end(log=logger)
def get_calendar_request(
    days: int = 14, instrument: Union[str, None] = None
) -> Union[pd.DataFrame, bool]:
    """Request data of significant events calendar.

    Parameters
    ----------
    instrument : Union[str, None]
        The loaded currency pair, by default None
    days : int
        Number of days in advance

    Returns
    -------
    Union[pd.DataFrame, bool]
        Calendar events data or False
    """
    if instrument is None:
        console.print(
            "Error: An instrument should be loaded before running this command."
        )
        return False
    parameters = {"instrument": instrument, "period": str(days * 86400 * -1)}

    if client is None:
        return False

    try:
        request = forexlabs.Calendar(params=parameters)
        response = client.request(request)
    except V20Error as e:
        logger.exception(str(e))
        d_error = json.loads(e.msg)
        console.print(d_error["message"], "\n")
        return False

    l_data = []
    for i in enumerate(response):
        if "forecast" in response[i[0]]:
            forecast = response[i[0]]["forecast"]
            if response[i[0]]["unit"] != "Index":
                forecast += response[i[0]]["unit"]
        else:
            forecast = ""

        if "market" in response[i[0]]:
            market = response[i[0]]["market"]
            if response[i[0]]["unit"] != "Index":
                market += response[i[0]]["unit"]
        else:
            market = ""

        if "actual" in response[i[0]]:
            actual = response[i[0]]["actual"]
            if response[i[0]]["unit"] != "Index":
                actual += response[i[0]]["unit"]
        else:
            actual = ""

        if "previous" in response[i[0]]:
            previous = response[i[0]]["previous"]
            if response[i[0]]["unit"] != "Index":
                previous += response[i[0]]["unit"]
        else:
            previous = ""

        if "impact" in response[i[0]]:
            impact = response[i[0]]["impact"]
        else:
            impact = ""

        l_data.append(
            {
                "Title": response[i[0]]["title"],
                "Time": datetime.fromtimestamp(response[i[0]]["timestamp"]),
                "Impact": impact,
                "Forecast": forecast,
                "Market Forecast": market,
                "Currency": response[i[0]]["currency"],
                "Region": response[i[0]]["region"],
                "Actual": actual,
                "Previous": previous,
            }
        )
    if len(l_data) == 0:
        df_calendar = pd.DataFrame()
    else:
        df_calendar = pd.DataFrame(l_data)
    return df_calendar
