"""Oanda View."""
__docformat__ = "numpy"

import logging
from typing import Dict, Union, Optional, List

import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import mplfinance as mpf
import pandas as pd
import pandas_ta as ta
import seaborn as sns

from openbb_terminal.config_terminal import theme
from openbb_terminal.config_plot import PLOT_DPI
from openbb_terminal.decorators import check_api_key
from openbb_terminal.decorators import log_start_end
from openbb_terminal.forex.oanda.oanda_model import (
    account_summary_request,
    cancel_pending_order_request,
    close_trades_request,
    create_order_request,
    fx_price_request,
    get_calendar_request,
    get_candles_dataframe,
    open_positions_request,
    open_trades_request,
    order_history_request,
    orderbook_plot_data_request,
    pending_orders_request,
    positionbook_plot_data_request,
)
from openbb_terminal.helper_funcs import plot_autoscale, is_valid_axes_count
from openbb_terminal.rich_config import console

logger = logging.getLogger(__name__)


@log_start_end(log=logger)
@check_api_key(["OANDA_ACCOUNT", "OANDA_TOKEN", "OANDA_ACCOUNT_TYPE"])
def get_fx_price(account: str, instrument: Union[str, None] = ""):
    """View price for loaded currency pair.

    Parameters
    ----------
    accountID : str
        Oanda account ID
    instrument : Union[str, None]
        Instrument code or None
    """
    data = fx_price_request(accountID=account, instrument=instrument)
    if data and data is not None and "prices" in data:
        bid = data["prices"][0]["bids"][0]["price"]
        ask = data["prices"][0]["asks"][0]["price"]
        console.print(f"{instrument if instrument else ''}" + " Bid: " + bid)
        console.print(f"{instrument if instrument else ''}" + " Ask: " + ask)
    else:
        console.print("No data was retrieved.\n")


@log_start_end(log=logger)
@check_api_key(["OANDA_ACCOUNT", "OANDA_TOKEN", "OANDA_ACCOUNT_TYPE"])
def get_account_summary(accountID: str):
    """Print Oanda account summary.

    Parameters
    ----------
    accountID : str
        Oanda user account ID
    """
    df_summary = account_summary_request(accountID)
    if df_summary is not False and not df_summary.empty:
        console.print(df_summary.to_string(index=False, header=False))
    else:
        console.print("No data was retrieved.\n")


@log_start_end(log=logger)
@check_api_key(["OANDA_ACCOUNT", "OANDA_TOKEN", "OANDA_ACCOUNT_TYPE"])
def get_order_book(
    accountID: str,
    instrument: str = "",
    external_axes: Optional[List[plt.Axes]] = None,
):
    """
    Plot the orderbook for the instrument if Oanda provides one.

    Parameters
    ----------
    accountID : str
        Oanda user account ID
    instrument : str
        The loaded currency pair
    external_axes : Optional[List[plt.Axes]], optional
        External axes (1 axis is expected in the list), by default None
    """
    df_orderbook_data = orderbook_plot_data_request(
        accountID=accountID, instrument=instrument
    )
    if df_orderbook_data is not False and not df_orderbook_data.empty:
        pd.set_option("display.max_rows", None)
        # HELP WANTED!
        # TODO:
        # An early contributor left "magic constants" in this function
        # help is needed to figure out the rationale behind these or
        # refactor it to not include the magic numbers.
        df_orderbook_data = df_orderbook_data.take(range(527, 727, 1))
        book_plot(
            df_orderbook_data, instrument, "Order Book", external_axes=external_axes
        )
    else:
        console.print("No data was retrieved.\n")


@log_start_end(log=logger)
@check_api_key(["OANDA_ACCOUNT", "OANDA_TOKEN", "OANDA_ACCOUNT_TYPE"])
def get_position_book(
    accountID: str, instrument: str = "", external_axes: Optional[List[plt.Axes]] = None
):
    """Plot a position book for an instrument if Oanda provides one.

    Parameters
    ----------
    accountID : str
        Oanda user account ID
    instrument : str
        The loaded currency pair
    external_axes : Optional[List[plt.Axes]], optional
        External axes (1 axis is expected in the list), by default None
    """
    df_positionbook_data = positionbook_plot_data_request(
        accountID=accountID, instrument=instrument
    )
    if df_positionbook_data is not False and not df_positionbook_data.empty:
        pd.set_option("display.max_rows", None)
        # HELP WANTED!
        # TODO:
        # An early contributor left "magic constants" in this function
        # help is needed to figure out the rationale behind these or
        # refactor it to not include the magic numbers.
        df_positionbook_data = df_positionbook_data.take(range(219, 300, 1))
        book_plot(
            df_positionbook_data,
            instrument,
            "Position Book",
            external_axes=external_axes,
        )
    else:
        console.print("No data was retrieved.\n")


@log_start_end(log=logger)
@check_api_key(["OANDA_ACCOUNT", "OANDA_TOKEN", "OANDA_ACCOUNT_TYPE"])
def list_orders(accountID: str, order_state: str = "PENDING", order_count: int = 0):
    """List order history.

    Parameters
    ----------
    accountID : str
        Oanda user account ID
    order_state : str
        Filter orders by a specific state ("PENDING", "CANCELLED", etc.)
    order_count : int
        Limit the number of orders to retrieve
    """
    df_order_list = order_history_request(order_state, order_count, accountID)
    if df_order_list is not False and not df_order_list.empty:
        console.print(df_order_list)
    else:
        console.print("No data was retrieved.\n")


@log_start_end(log=logger)
@check_api_key(["OANDA_ACCOUNT", "OANDA_TOKEN", "OANDA_ACCOUNT_TYPE"])
def create_order(accountID: str, instrument: str = "", price: int = 0, units: int = 0):
    """Create a buy/sell order.

    Parameters
    ----------
    accountID : str
        Oanda user account ID
    instrument : str
        The loaded currency pair
    price : int
        The price to set for the limit order.
    units : int
        The number of units to place in the order request.
    """
    df_orders = create_order_request(price, units, instrument, accountID)
    if df_orders is not False and not df_orders.empty:
        console.print(df_orders.to_string(index=False))
    else:
        console.print("No data was returned from Oanda.\n")


@log_start_end(log=logger)
@check_api_key(["OANDA_ACCOUNT", "OANDA_TOKEN", "OANDA_ACCOUNT_TYPE"])
def cancel_pending_order(accountID: str, orderID: str = ""):
    """Cancel a Pending Order.

    Parameters
    ----------
    accountID : str
        Oanda user account ID
    orderID : str
        The pending order ID to cancel.
    """
    order_id = cancel_pending_order_request(orderID, accountID)
    if order_id is not False and order_id is not None:
        console.print(f"Order {order_id} canceled.")
    else:
        console.print("No data was returned from Oanda.\n")


@log_start_end(log=logger)
@check_api_key(["OANDA_ACCOUNT", "OANDA_TOKEN", "OANDA_ACCOUNT_TYPE"])
def get_open_positions(accountID: str):
    """Get information about open positions.

    Parameters
    ----------
    accountID : str
        Oanda user account ID
    """
    df_positions = open_positions_request(accountID)
    if df_positions is not False and not df_positions.empty:
        console.print(df_positions.to_string(index=False))
    else:
        console.print("No data was returned from Oanda.\n")


@log_start_end(log=logger)
@check_api_key(["OANDA_ACCOUNT", "OANDA_TOKEN", "OANDA_ACCOUNT_TYPE"])
def get_pending_orders(accountID: str):
    """Get information about pending orders.

    Parameters
    ----------
    accountID : str
        Oanda user account ID
    """
    df_pending = pending_orders_request(accountID)
    if df_pending is not False and not df_pending.empty:
        console.print(df_pending.to_string(index=False))
    elif df_pending is not False and df_pending.empty:
        console.print("No pending orders.\n")
    else:
        console.print("No data was returned from Oanda.\n")


@log_start_end(log=logger)
@check_api_key(["OANDA_ACCOUNT", "OANDA_TOKEN", "OANDA_ACCOUNT_TYPE"])
def get_open_trades(accountID: str):
    """View open trades.

    Parameters
    ----------
    accountID : str
        Oanda user account ID
    """
    df_trades = open_trades_request(accountID)
    if isinstance(df_trades, pd.DataFrame) and not df_trades.empty:
        console.print(df_trades.to_string(index=False))
    elif df_trades is not False and df_trades.empty:
        console.print("No trades were found.\n")
    else:
        console.print("No data was returned from Oanda.\n")


@log_start_end(log=logger)
@check_api_key(["OANDA_ACCOUNT", "OANDA_TOKEN", "OANDA_ACCOUNT_TYPE"])
def close_trade(accountID: str, orderID: str = "", units: Union[int, None] = None):
    """Close a trade.

    Parameters
    ----------
    accountID : str
        Oanda user account ID
    orderID : str
        ID of the order to close
    units : Union[int, None]
        Number of units to close. If empty default to all.
    """
    df_trades = close_trades_request(orderID, units, accountID)
    if df_trades is not False and not df_trades.empty:
        console.print(df_trades.to_string(index=False))
    elif df_trades is not False and df_trades.empty:
        console.print("No trades were found.\n")
    else:
        console.print("No data was returned from Oanda.\n")


@log_start_end(log=logger)
@check_api_key(["OANDA_ACCOUNT", "OANDA_TOKEN", "OANDA_ACCOUNT_TYPE"])
def show_candles(
    instrument: str = "",
    granularity: str = "D",
    candlecount: int = 180,
    additional_charts: Optional[Dict[str, bool]] = None,
    external_axes: Optional[List[plt.Axes]] = None,
):
    """Show candle chart.

    Note that additional plots (ta indicators) not supported in external axis mode.

    Parameters
    ----------
    instrument : str
        The loaded currency pair
    granularity : str, optional
        The timeframe to get for the candle chart. Seconds: S5, S10, S15, S30
        Minutes: M1, M2, M4, M5, M10, M15, M30 Hours: H1, H2, H3, H4, H6, H8, H12
        Day (default): D, Week: W Month: M,
    candlecount : int, optional
        Limit for the number of data points
    additional_charts : Dict[str, bool]
        A dictionary of flags to include additional charts
    external_axes : Optional[List[plt.Axes]], optional
        External axes (2 axes are expected in the list), by default None
    """
    df_candles = get_candles_dataframe(instrument, granularity, candlecount)
    if (
        df_candles is not False
        and not df_candles.empty
        and additional_charts is not None
    ):
        plots_to_add, legends, subplot_legends = add_plots(
            df_candles, additional_charts
        )
    else:
        plots_to_add, legends, subplot_legends = None, [], []

    candle_chart_kwargs = {
        "type": "candle",
        "style": theme.mpf_style,
        "mav": (20, 50),
        "volume": True,
        "xrotation": theme.xticks_rotation,
        "scale_padding": {"left": 0.3, "right": 1, "top": 0.8, "bottom": 0.8},
        "update_width_config": {
            "candle_linewidth": 0.6,
            "candle_width": 0.8,
            "volume_linewidth": 0.8,
            "volume_width": 0.8,
        },
        "warn_too_much_data": 10000,
    }
    # This plot has 2 axes
    if not external_axes:
        candle_chart_kwargs["returnfig"] = True
        candle_chart_kwargs["figratio"] = (10, 7)
        candle_chart_kwargs["figscale"] = 1.10
        candle_chart_kwargs["figsize"] = plot_autoscale()
        if plots_to_add:
            candle_chart_kwargs["addplot"] = plots_to_add
        if isinstance(df_candles, pd.DataFrame):
            fig, ax = mpf.plot(df_candles, **candle_chart_kwargs)
            fig.suptitle(
                f"{instrument} {granularity}",
                x=0.055,
                y=0.965,
                horizontalalignment="left",
            )
            if len(legends) > 0:
                ax[0].legend(legends)
            # pylint: disable=C0200
            for i in range(0, len(subplot_legends), 2):
                ax[subplot_legends[i]].legend(subplot_legends[i + 1])
            theme.visualize_output(force_tight_layout=False)
        else:
            logger.error("Data not found")
            console.print("[red]Data not found[/red]\n")
    elif is_valid_axes_count(external_axes, 2):
        ax, volume = external_axes
        candle_chart_kwargs["ax"] = ax
        candle_chart_kwargs["volume"] = volume
        mpf.plot(df_candles, **candle_chart_kwargs)
    else:
        return


@log_start_end(log=logger)
@check_api_key(["OANDA_ACCOUNT", "OANDA_TOKEN", "OANDA_ACCOUNT_TYPE"])
def calendar(instrument: str, days: int = 7):
    """View calendar of significant events.

    Parameters
    ----------
    instrument : str
        The loaded currency pair
    days : int
        Number of days in advance
    """
    df_calendar = get_calendar_request(days, instrument)
    if df_calendar is not False and not df_calendar.empty:
        console.print(df_calendar.to_string(index=False))
    elif df_calendar is not False and df_calendar.empty:
        console.print("No calendar records were found.\n")
    else:
        console.print("No data was returned from Oanda.\n")


# Utilities


@log_start_end(log=logger)
def add_plots(df: pd.DataFrame, additional_charts: Dict[str, bool]):
    """Add additional plots to the candle chart.

    Parameters
    ----------
    df : pd.DataFrame
        The source data
    additional_charts : Dict[str, bool]
        A dictionary of flags to include additional charts

    Returns
    -------
    Tuple
        Tuple of lists containing the plots, legends and subplot legends
    """
    panel_number = 2
    plots_to_add = []
    legends = []
    subplot_legends = []

    if additional_charts["ad"]:
        ad = ta.ad(df["High"], df["Low"], df["Close"], df["Volume"])
        ad_plot = mpf.make_addplot(ad, panel=panel_number)
        plots_to_add.append(ad_plot)
        subplot_legends.extend([panel_number * 2, ["AD"]])
        panel_number += 1

    if additional_charts["bbands"]:
        bbands = ta.bbands(df["Close"])
        bbands = bbands.drop("BBB_5_2.0", axis=1)
        bbands_plot = mpf.make_addplot(bbands, panel=0)
        plots_to_add.append(bbands_plot)
        legends.extend(["Lower BBand", "Middle BBand", "Upper BBand"])

    if additional_charts["cci"]:
        cci = ta.cci(df["High"], df["Low"], df["Close"])
        cci_plot = mpf.make_addplot(cci, panel=panel_number)
        plots_to_add.append(cci_plot)
        subplot_legends.extend([panel_number * 2, ["CCI"]])
        panel_number += 1

    if additional_charts["ema"]:
        ema = ta.ema(df["Close"])
        ema_plot = mpf.make_addplot(ema, panel=0)
        plots_to_add.append(ema_plot)
        legends.append("10 EMA")

    if additional_charts["rsi"]:
        rsi = ta.rsi(df["Close"])
        rsi_plot = mpf.make_addplot(rsi, panel=panel_number)
        plots_to_add.append(rsi_plot)
        subplot_legends.extend([panel_number * 2, ["RSI"]])
        panel_number += 1

    if additional_charts["obv"]:
        obv = ta.obv(df["Close"], df["Volume"])
        obv_plot = mpf.make_addplot(obv, panel=panel_number)
        plots_to_add.append(obv_plot)
        subplot_legends.extend([panel_number * 2, ["OBV"]])
        panel_number += 1

    if additional_charts["sma"]:
        sma_length = [20, 50]
        for length in sma_length:
            sma = ta.sma(df["Close"], length=length)
            sma_plot = mpf.make_addplot(sma, panel=0)
            plots_to_add.append(sma_plot)
            legends.append(f"{length} SMA")

    if additional_charts["vwap"]:
        vwap = ta.vwap(df["High"], df["Low"], df["Close"], df["Volume"])
        vwap_plot = mpf.make_addplot(vwap, panel=0)
        plots_to_add.append(vwap_plot)
        legends.append("vwap")

    return plots_to_add, legends, subplot_legends


@log_start_end(log=logger)
def book_plot(
    df: pd.DataFrame,
    instrument: str,
    book_type: str,
    external_axes: Optional[List[plt.Axes]] = None,
):
    """Plot the order book for a given instrument.

    Parameters
    ----------
    df : pd.DataFrame
        Order book data
    instrument : str
        The loaded currency pair
    book_type : str
        Order book type
    external_axes : Optional[List[plt.Axes]], optional
        External axes (1 axis is expected in the list), by default None
    """
    df = df.apply(pd.to_numeric)
    df["shortCountPercent"] = df["shortCountPercent"] * -1
    axis_origin = max(
        abs(max(df["longCountPercent"])), abs(max(df["shortCountPercent"]))
    )

    # This plot has 1 axis
    if not external_axes:
        _, ax = plt.subplots(figsize=plot_autoscale(), dpi=PLOT_DPI)
    elif is_valid_axes_count(external_axes, 1):
        (ax,) = external_axes
    else:
        return

    ax.set_xlim(-axis_origin, +axis_origin)

    sns.barplot(
        x="longCountPercent",
        y="price",
        data=df,
        label="Count Percent",
        color=theme.up_color,
        orient="h",
    )

    sns.barplot(
        x="shortCountPercent",
        y="price",
        data=df,
        label="Prices",
        color=theme.down_color,
        orient="h",
    )

    ax.invert_yaxis()
    ax.yaxis.set_major_locator(mticker.MultipleLocator(10))
    ax.set_xlabel("Count Percent")
    ax.set_ylabel("Price")
    ax.set_title(f"{instrument} {book_type}")

    theme.style_primary_axis(ax)

    if not external_axes:
        theme.visualize_output()
