from typing import Literal, Union

import matplotlib.pyplot as plt
import pandas as pd
from matplotlib.axes import Axes

from .apply_bsic_logo import apply_bsic_logo
from .apply_bsic_style import apply_bsic_style
from .format_timeseries_axis import format_timeseries_axis


def _plot_xline(ax: Axes, y: float, trade_start: str, color: str):
    bbox_props = dict(boxstyle="round", fc="w", ec="k", lw=1)
    y_min, y_max = ax.get_ylim()

    ax.hlines(
        y=[y],
        xmin=trade_start,
        xmax=ax.get_xbound()[1],
        color=color,
        linewidth=1,
        linestyle="-",
        alpha=0.75,
    )
    ax.annotate(
        str(round(y, 2)),
        xycoords="axes fraction",
        xy=(1.02, (y - y_min) / (y_max - y_min)),
        size=8,
        bbox=bbox_props,
        verticalalignment="center",
    )


def _plot_last_price(ax: Axes, data: pd.Series):
    """Plots the last price of the series on the right side of the plot."""
    # plot last price
    last_price = float(data.iloc[-1])
    y_min, y_max = ax.get_ylim()

    bbox_props = dict(boxstyle="round", fc="w", ec="k", lw=0.75)
    ax.annotate(
        str(int(last_price)),
        xycoords="axes fraction",
        xy=(1.02, (last_price - y_min) / (y_max - y_min)),
        size=8,
        bbox=bbox_props,
        verticalalignment="center",
    )


def _get_dates(
    underlying: pd.Series, pnl: pd.Series, months_offset: int
) -> tuple[pd.DatetimeIndex, pd.Timestamp]:
    """Gets the dates for the trade."""
    if not isinstance(underlying.index, pd.DatetimeIndex):
        raise Exception("Index of underlying must be a DatetimeIndex")

    if not isinstance(pnl.index, pd.DatetimeIndex):
        raise Exception("Index of PnL must be a DatetimeIndex")

    dates = underlying.index

    end_date = dates[-1]
    start_date = end_date - pd.DateOffset(months=months_offset)
    entry_date = pnl.dropna().index[0]

    dates = dates[(dates >= start_date) & (dates <= end_date)]

    if not isinstance(entry_date, pd.Timestamp):
        raise Exception("There was an issue pulling the entry date for the trade.")

    return dates, entry_date


def _plot_entry_point(
    ax: Axes,
    entry_date: pd.Timestamp,
    underlying: pd.Series,
    marker_location: Literal["top", "bottom"],
    marker_size: int,
):
    """Plots the entry point of the trade."""

    offset = 0.075 if marker_location == "top" else -0.075
    marker = "v" if marker_location == "top" else "^"

    price_at_entry = underlying.loc[entry_date]
    marker_loc = price_at_entry * (1 + offset)

    ax.scatter(
        [entry_date.strftime("%Y-%m-%d")],
        [marker_loc],
        color="g",
        marker=marker,
        s=marker_size,
    )


def _preprocess_data(
    underlying: pd.Series, pnl: pd.Series, dates: pd.DatetimeIndex, pnl_type: str
):
    """Preprocesses the data."""
    underlying = underlying.copy().loc[dates]
    pnl = pnl.copy()

    if pnl_type == "nominal":
        pass
    elif pnl_type == "cumulative":
        pnl = pnl.cumsum()
    else:
        raise Exception(
            'pnl type is not supported. Supported are "nominal" and "cumulative".'
        )

    pnl.fillna(0, inplace=True)

    return underlying, pnl


def plot_trade(
    underlying: pd.Series,
    pnl: pd.Series,
    pnl_type: Literal["nominal", "cumulative"],
    title: str,
    underlying_name: str,
    months_offset: int = 3,
    sources: Union[str, list[str]] = "BSIC",
    entry_point_marker_loc: Literal["top", "bottom"] = "top",
    entry_point_marker_size: int = 10,
    date_ticks_unit: Literal["Y", "M", "W", "D"] = "W",
    date_ticks_freq: int = 1,
    date_ticks_format: str = "%b %d, %Y",
):
    """Plot a trade performance vs the underlying.

    Create a figure with two subplots. On the top, the PnL of the trade is plotted,
    while on the bottom the underlying is plotted.

    The function automatically applies BSIC Style and
    Logo through the respective functions.

    Parameters
    ----------
    underlying : pd.Series
        Pandas series of the underlying's prices.
    pnl : pd.Series
        Pandas series of the pnl of the trade. This must be in nominal terms
        or in percentage terms,
        if you want to plot the percentages. You can also plot the cumulative PnL,
        the function will calculate it for you.
    pnl_type : Literal['Nominal', 'Cumulative']
        The type of PnL you want to plot. Can be "Nominal" or "Cumulative".
        If "nominal", the function will plot the PnL as is.
        If "cumulative", the function will plot the cumulative PnL by doing
        `.cumsum()` on the PnL series.
    title : str
        The title of the trade. This will be set as the suptitle of the figure.
    underlying_name : str
        The name of the underlying that will be plotted on the bottom subplot,
        for example "Swap Spread", "EURUSD", etc.
    months_offset : int, optional
        The months offset that will be used to calculate the first date plotted,
        by default 3. Since it is a trade,
        it is recommended to set a short offset (like 3 as the default),
        so that you can easily see the path of the underlying
        after you entered the trade.
    sources : str | list[str], optional
        List of sources, by default "BSIC".
        You can either specify a string (if you have only one source)
        or a list of strings (multiple sources).

        Since BSIC is always a source, it will always be included.

        **NB**: if when calling ``plt.show()`` the text seems cutted out, don't worry.
        When exporting using ``bbox_inches="tight"``,
        it will seamlessly fit within the figure.
        This happens because I want to make sure
        there is enough space between the plot and the sources text,
        so I position the text at the very bottom of the figure.
    entry_point_marker_loc : Literal['top', 'bottom'], optional
        The location of the marker for the entry point, by default "top".
        Whether it is better to use "top" or "bottom" depends
        on the type of trade you are plotting and the shape of the plot.
        Choose so that the marker is clearly visible and
        does not overlap with the underlying's plot.
    entry_point_marker_size : int, optional
        The size of the entry point market, by default 10. Choosing this size will
        depend on the final size of the plot.
        Choose so that the marker is clearly visible and does not overlap with the
        underlying's plot.
    date_ticks_unit : Literal['Y', 'M', 'W', 'D'], optional
        The unit used to segment the datetime index (which is displayed
        on the bottom axis), by default "W". Since you
        are probably going to plot a short timespan, it is recommended to use either
        "W" (weeks) or "D" (days). There should
        be no reason to use "Y" (years), and you should really question the timeframe
        you are using if you are choosing "M".
    date_ticks_freq : int, optional
        The frequency for the ticks in the unit specified, by default 1. For example,
        if you chose "W" as the unit, and 1 as the
        frequency, there will be 1 tick every week.
        Choose so that the axis is not cluttered.
    date_ticks_format : str, optional
        The format used for the date ticks, by default ``"%b %d, %Y"``. I recommend
        using the default, but you can change it to whatever
        you find more suitable.

    Returns
    -------
    tuple[matplotlib.figure.Figure, matplotlib.axes.Axes]
        A tuple containing the figure and the tuple of two axis just created.
        You can later use this to save the fig for export, do ``fig.show()``,
        or further customize the plot.

    See Also
    --------
    mpl_bsic.apply_bsic_style :
        Apply the BSIC style to a plot.
    mpl_bsic.apply_bsic_logo :
        Apply the BSIC logo to a plot.
    mpl_bsic.check_figsize :
        Check if the figsize is valid.

    Examples
    --------
    Examples will come soon.
    """
    dates, entry_date = _get_dates(underlying, pnl, months_offset)

    underlying, pnl = _preprocess_data(underlying, pnl, dates, pnl_type)

    # creates plots
    fig, axs = plt.subplots(
        2, 1, sharex=True, gridspec_kw={"hspace": 0.1, "height_ratios": [1.25, 2]}
    )
    pnl_ax, underlying_ax = axs
    pnl_ax: Axes
    underlying_ax: Axes

    # sets title and applies style
    if title is not None:
        fig.suptitle(title)

    apply_bsic_style(fig, axs, sources)

    # plot the data
    underlying_ax.plot(underlying.index, underlying)
    pnl_ax.plot(pnl.index, pnl)
    pnl_ax.axhline(0, color="black", linewidth=1, alpha=0.75)

    # set labels
    underlying_ax.set_ylabel(underlying_name)
    pnl_ax.set_ylabel("PnL")

    # formats dates
    format_timeseries_axis(
        underlying_ax, date_ticks_unit, date_ticks_freq, date_ticks_format
    )

    # apply logo
    apply_bsic_logo(fig, pnl_ax, location="top left")

    # plot last price
    _plot_last_price(underlying_ax, underlying)
    _plot_last_price(pnl_ax, pnl)

    # plot entry point
    _plot_entry_point(
        underlying_ax,
        entry_date,
        underlying,
        entry_point_marker_loc,
        entry_point_marker_size,
    )

    # plot areas of profit and loss
    pnl_ax.fill_between(
        pnl.index,
        0,
        pnl,
        where=(pnl >= 0),  # type: ignore
        color="g",
        alpha=0.3,
        interpolate=True,
        lw=0,
    )
    pnl_ax.fill_between(
        pnl.index,
        0,
        pnl,
        where=(pnl < 0),  # type: ignore
        color="r",
        alpha=0.3,
        interpolate=True,
    )

    # # plot stop loss and take profit
    # trade_start = pnl[pnl["pnl"] > 0].index[-1]
    # print(trade_start, pnl, pnl[pnl > 0])
    # if stop_loss is not None:
    #     _plot_xline(underlying_ax, stop_loss, trade_start=trade_start, color="r")
    # if take_profit is not None:
    #     _plot_xline(underlying_ax, take_profit, trade_start=trade_start, color="g")

    # underlying_ax.set_xlim(xlims)
    fig.subplots_adjust(bottom=0.2)  # leave space for the sources

    return fig, axs
