""" FRED model """
__docformat__ = "numpy"

# IMPORTATION STANDARD
import logging
import os
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Optional, Tuple

# IMPORTATION THIRDPARTY
import certifi
import numpy as np
import pandas as pd
from fredapi import Fred
from requests import HTTPError

# IMPORTATION INTERNAL
from openbb_terminal.core.session.current_user import get_current_user
from openbb_terminal.decorators import check_api_key, log_start_end
from openbb_terminal.rich_config import console

# pylint: disable=attribute-defined-outside-init,dangerous-default-value
# pylint: disable=C0302

logger = logging.getLogger(__name__)

ice_bofa_path = (Path(__file__).parent / "ice_bofa_indices.csv").resolve()
commercial_paper_path = (Path(__file__).parent / "commercial_paper.csv").resolve()
spot_rates_path = (Path(__file__).parent / "corporate_spot_rates.csv").resolve()

YIELD_CURVE_SERIES_NOMINAL = {
    "1Month": "DGS1MO",
    "3Month": "DGS3MO",
    "6Month": "DGS6MO",
    "1Year": "DGS1",
    "2Year": "DGS2",
    "3Year": "DGS3",
    "5Year": "DGS5",
    "7Year": "DGS7",
    "10Year": "DGS10",
    "20Year": "DGS20",
    "30Year": "DGS30",
}
YIELD_CURVE_SERIES_REAL = {
    "5Year": "DFII5",
    "7Year": "DFII7",
    "10Year": "DFII10",
    "20Year": "DFII20",
    "30Year": "DFII30",
}

YIELD_CURVE_SERIES_CORPORATE_SPOT = {
    "6Month": "HQMCB6MT",
    "1Year": "HQMCB1YR",
    "2Year": "HQMCB2YR",
    "3Year": "HQMCB3YR",
    "5Year": "HQMCB5YR",
    "7Year": "HQMCB7YR",
    "10Year": "HQMCB10YR",
    "20Year": "HQMCB20YR",
    "30Year": "HQMCB30YR",
    "50Year": "HQMCB50YR",
    "75Year": "HQMCB75YR",
    "100Year": "HQMCB100YR",
}
YIELD_CURVE_SERIES_CORPORATE_PAR = {
    "2Year": "HQMCB2YRP",
    "5Year": "HQMCB5YRP",
    "10Year": "HQMCB10YRP",
    "30Year": "HQMCB30YRP",
}

YIELD_CURVE_NOMINAL_RATES = [1 / 12, 0.25, 0.5, 1, 2, 3, 5, 7, 10, 20, 30]
YIELD_CURVE_SPOT_RATES = [0.5, 1, 2, 3, 5, 7, 10, 20, 30, 50, 75, 100]
YIELD_CURVE_REAL_RATES = [5, 7, 10, 20, 30]
YIELD_CURVE_PAR_RATES = [2, 5, 10, 30]

ice_bofa_path = (Path(__file__).parent / "ice_bofa_indices.csv").resolve()
commercial_paper_path = (Path(__file__).parent / "commercial_paper.csv").resolve()
spot_rates_path = (Path(__file__).parent / "corporate_spot_rates.csv").resolve()


NAME_TO_ID_PROJECTION = {
    "Range High": ["FEDTARRH", "FEDTARRHLR"],
    "Central tendency High": ["FEDTARCTH", "FEDTARCTHLR"],
    "Median": ["FEDTARMD", "FEDTARMDLR"],
    "Range Midpoint": ["FEDTARRM", "FEDTARRMLR"],
    "Central tendency Midpoint": ["FEDTARCTM", "FEDTARCTMLR"],
    "Range Low": ["FEDTARRL", "FEDTARRLLR"],
    "Central tendency Low": ["FEDTARCTL", "FEDTARCTLLR"],
}

NAME_TO_ID_ECB = {"deposit": "ECBDFR", "lending": "ECBMLFR", "refinancing": "ECBMRRFR"}

USARATES_TO_FRED_ID = {
    "4_week": {"tbill": "DTB4WK"},
    "1_month": {"cmn": "DGS1MO"},
    "3_month": {"tbill": "TB3MS", "cmn": "DGS3MO"},
    "6_month": {"tbill": "DTB6", "cmn": "DGS6MO"},
    "1_year": {"tbill": "DTB1YR", "cmn": "DGS1"},
    "2_year": {"cmn": "DGS2"},
    "3_year": {"cmn": "DGS3"},
    "5_year": {"tips": "DFII5", "cmn": "DGS5"},
    "7_year": {"tips": "DFII7", "cmn": "DGS7"},
    "10_year": {"tips": "DFII10", "cmn": "DGS10"},
    "20_year": {"tips": "DFII20", "cmn": "DGS20"},
    "30_year": {"tips": "DFII30", "cmn": "DGS30"},
}

ICE_BOFA_TO_OPTIONS = {
    "Type": ["total_return", "yield", "yield_to_worst"],
    "Category": ["all", "duration", "eur", "usd"],
    "Area": ["asia", "emea", "eu", "ex_g10", "latin_america", "us"],
    "Grade": [
        "a",
        "aa",
        "aaa",
        "b",
        "bb",
        "bbb",
        "ccc",
        "crossover",
        "high_grade",
        "high_yield",
        "non_financial",
        "non_sovereign",
        "private_sector",
        "public_sector",
    ],
}

MOODY_TO_OPTIONS: Dict[str, Dict] = {
    "Type": {
        "aaa": {
            "index": {
                "id": "DAAA",
                "name": "Moody's Seasoned Aaa Corporate Bond Yield",
            },
            "treasury": {
                "id": "AAA10Y",
                "name": "Moody's Seasoned Aaa Corporate Bond Yield Relative to Yield "
                "on 10-Year Treasury Constant Maturity",
            },
            "fed_funds": {
                "id": "AAAFF",
                "name": "Moody's Seasoned Aaa Corporate Bond Minus Federal Funds Rate",
            },
        },
        "baa": {
            "index": {
                "id": "DBAA",
                "name": "Moody's Seasoned Baa Corporate Bond Yield",
            },
            "treasury": {
                "id": "BAA10Y",
                "name": "Moody's Seasoned Baa Corporate Bond Yield Relative "
                "to Yield on 10-Year Treasury Constant Maturity",
            },
            "fed_funds": {
                "id": "BAAFF",
                "name": "Moody's Seasoned Baa Corporate Bond Minus Federal Funds Rate",
            },
        },
    },
    "Spread": ["treasury", "fed_funds"],  # type: ignore
}
SPOT_TO_OPTIONS = {
    "Maturity": [f"{i}y" for i in np.arange(1, 100.5, 0.5)],
    "Category": ["spot_rate", "par_yield"],
}
CP_TO_OPTIONS = {
    "Maturity": ["15d", "30d", "60d", "7d", "90d", "overnight"],
    "Category": ["asset_backed", "financial", "non_financial", "spread"],
    "Grade": ["a2_p2", "aa"],
}

ESTR_PARAMETER_TO_ECB_ID = {
    "volume_weighted_trimmed_mean_rate": "ECBESTRVOLWGTTRMDMNRT",
    "number_of_transactions": "ECBESTRNUMTRANS",
    "number_of_active_banks": "ECBESTRNUMACTBANKS",
    "total_volume": "ECBESTRTOTVOL",
    "share_of_volume_of_the_5_largest_active_banks": "ECBESTRSHRVOL5LRGACTBNK",
    "rate_at_75th_percentile_of_volume": "ECBESTRRT75THPCTVOL",
    "rate_at_25th_percentile_of_volume": "ECBESTRRT25THPCTVOL",
}

SOFR_PARAMETER_TO_FRED_ID = {
    "overnight": "SOFR",
    "30_day_average": "SOFR30DAYAVG",
    "90_day_average": "SOFR90DAYAVG",
    "180_day_average": "SOFR180DAYAVG",
    "index": "SOFRINDEX",
}
SONIA_PARAMETER_TO_FRED_ID = {
    "rate": "IUDSOIA",
    "index": "IUDZOS2",
    "10th_percentile": "IUDZLS6",
    "25th_percentile": "IUDZLS7",
    "75th_percentile": "IUDZLS8",
    "90th_percentile": "IUDZLS9",
    "total_nominal_value": "IUDZLT2",
}
AMERIBOR_PARAMETER_TO_FRED_ID = {
    "overnight": "AMERIBOR",
    "term_30": "AMBOR30T",
    "term_90": "AMBOR90T",
    "1_week_term_structure": "AMBOR1W",
    "1_month_term_structure": "AMBOR1M",
    "3_month_term_structure": "AMBOR3M",
    "6_month_term_structure": "AMBOR6M",
    "1_year_term_structure": "AMBOR1Y",
    "2_year_term_structure": "AMBOR2Y",
    "30_day_ma": "AMBOR30",
    "90_day_ma": "AMBOR90",
}

FED_PARAMETER_TO_FRED_ID = {
    "monthly": "FEDFUNDS",
    "daily": "DFF",
    "weekly": "FF",
    "daily_excl_weekend": "RIFSPFFNB",
    "annual": "RIFSPFFNA",
    "biweekly": "RIFSPFFNBWAW",
    "volume": "EFFRVOL",
}

OBFR_PARAMETER_TO_FRED_ID = {
    "daily": "OBFR",
    "volume": "OBFRVOL",
}

DWPCR_PARAMETER_TO_FRED_ID = {
    "daily_excl_weekend": "DPCREDIT",
    "monthly": "MPCREDIT",
    "weekly": "WPCREDIT",
    "daily": "RIFSRPF02ND",
    "annual": "RIFSRPF02NA",
}
TMC_PARAMETER_TO_FRED_ID = {
    "3_month": "T10Y3M",
    "2_year": "T10Y2Y",
}
FFRMC_PARAMETER_TO_FRED_ID = {
    "10_year": "T10YFF",
    "5_year": "T5YFF",
    "1_year": "T1YFF",
    "6_month": "T6MFF",
    "3_month": "T3MFF",
}

TBFFR_PARAMETER_TO_FRED_ID = {
    "3_month": "TB3SMFFM",
    "6_month": "TB6SMFFM",
}


@log_start_end(log=logger)
@check_api_key(["API_FRED_KEY"])
def get_series_data(
    series_id: str, start_date: Optional[str] = None, end_date: Optional[str] = None
) -> pd.DataFrame:
    """Get Series data. [Source: FRED]

    Parameters
    ----------
    series_id : str
        Series ID to get data from
    start_date : Optional[str]
        Start date to get data from, format yyyy-mm-dd
    end_date : Optional[str]
        End data to get from, format yyyy-mm-dd

    Returns
    -------
    pd.DataFrame
        Series data
    """
    try:
        # Necessary for installer so that it can locate the correct certificates for
        # API calls and https
        # https://stackoverflow.com/questions/27835619/urllib-and-ssl-certificate-verify-failed-error/73270162#73270162
        os.environ["REQUESTS_CA_BUNDLE"] = certifi.where()
        os.environ["SSL_CERT_FILE"] = certifi.where()
        fredapi_client = Fred(get_current_user().credentials.API_FRED_KEY)
        df = fredapi_client.get_series(series_id, start_date, end_date)
    # Series does not exist & invalid api keys
    except HTTPError as e:
        console.print(e)

    return df


@log_start_end(log=logger)
@check_api_key(["API_FRED_KEY"])
def get_yield_curve(
    date: str = "",
    return_date: bool = False,
    inflation_adjusted: bool = False,
    spot_or_par: Optional[str] = None,
) -> Tuple[pd.DataFrame, str]:
    """Gets yield curve data from FRED.

    The graphic depiction of the relationship between the yield on bonds of the same credit quality but different
    maturities is known as the yield curve. In the past, most market participants have constructed yield curves from
    the observations of prices and yields in the Treasury market. Two reasons account for this tendency. First,
    Treasury securities are viewed as free of default risk, and differences in creditworthiness do not affect yield
    estimates. Second, as the most active bond market, the Treasury market offers the fewest problems of illiquidity
    or infrequent trading. The key function of the Treasury yield curve is to serve as a benchmark for pricing bonds
    and setting yields in other sectors of the debt market.

    It is clear that the market’s expectations of future rate changes are one important determinant of the
    yield-curve shape. For example, a steeply upward-sloping curve may indicate market expectations of near-term Fed
    tightening or of rising inflation. However, it may be too restrictive to assume that the yield differences across
    bonds with different maturities only reflect the market’s rate expectations. The well-known pure expectations
    hypothesis has such an extreme implication. The pure expectations hypothesis asserts that all government bonds
    have the same near-term expected return (as the nominally riskless short-term bond) because the return-seeking
    activity of risk-neutral traders removes all expected return differentials across bonds.

    Parameters
    ----------
    date: str
        Date to get curve for. If empty, gets most recent date (format yyyy-mm-dd)
    return_date: bool
        If True, returns date of yield curve

    Returns
    -------
    Tuple[pd.DataFrame, str]
        Dataframe of yields and maturities,
        Date for which the yield curve is obtained

    Examples
    --------
    >>> from openbb_terminal.sdk import openbb
    >>> ycrv_df = openbb.fixedincome.ycrv()

    Since there is a delay with the data, the most recent date is returned and can be accessed with return_date=True
    >>> ycrv_df, ycrv_date = openbb.fixedincome.ycrv(return_date=True)
    """

    # Necessary for installer so that it can locate the correct certificates for
    # API calls and https
    # https://stackoverflow.com/questions/27835619/urllib-and-ssl-certificate-verify-failed-error/73270162#73270162
    # os.environ["REQUESTS_CA_BUNDLE"] = certifi.where()
    # os.environ["SSL_CERT_FILE"] = certifi.where()

    fredapi_client = Fred(get_current_user().credentials.API_FRED_KEY)

    df = pd.DataFrame()

    # Check that the date is in the past
    today = datetime.now().strftime("%Y-%m-%d")
    if date and date >= today:
        console.print("[red]Date cannot be today or in the future[/red]")
        if return_date:
            return pd.DataFrame(), date
        return pd.DataFrame()

    # Add in logic that will get the most recent date
    if date:
        get_last = False
        start_date = (
            datetime.strptime(date, "%Y-%m-%d") - timedelta(days=50)
        ).strftime("%Y-%m-%d")
    else:
        date = datetime.now().strftime("%Y-%m-%d")
        start_date = (datetime.now() - timedelta(days=50)).strftime("%Y-%m-%d")
        get_last = True

    if inflation_adjusted:
        fred_series = YIELD_CURVE_SERIES_REAL
        years = YIELD_CURVE_REAL_RATES
    elif spot_or_par:
        if spot_or_par == "spot":
            years = YIELD_CURVE_SPOT_RATES  # type: ignore
            fred_series = YIELD_CURVE_SERIES_CORPORATE_SPOT
        elif spot_or_par == "par":
            years = YIELD_CURVE_PAR_RATES
            fred_series = YIELD_CURVE_SERIES_CORPORATE_PAR
        else:
            console.print("Please select either 'spot' or 'par' rates.")
    else:
        fred_series = YIELD_CURVE_SERIES_NOMINAL
        years = YIELD_CURVE_NOMINAL_RATES  # type: ignore

    for key, s_id in fred_series.items():
        df = pd.concat(
            [
                df,
                pd.DataFrame(
                    fredapi_client.get_series(s_id, start_date), columns=[key]
                ),
            ],
            axis=1,
        )
    if df.empty:
        if return_date:
            return pd.DataFrame(), date
        return pd.DataFrame()
    # Drop rows with NaN -- corresponding to weekends typically
    df = df.dropna()

    if date not in df.index or get_last:
        # If the get_last flag is true, we want the first date, otherwise we want the last date.
        idx = -1 if get_last else 0
        date_of_yield = df.index[idx].strftime("%Y-%m-%d")
        rates = pd.DataFrame(df.iloc[idx, :].values, columns=["Rate"])

        if spot_or_par:
            console.print(
                f"Because {spot_or_par.title()} rates are published monthly, "
                f"the nearest date to {date} is used which is {date_of_yield}."
            )
    else:
        date_of_yield = date
        series = df[df.index == date]
        if series.empty:
            return pd.DataFrame(), date_of_yield
        rates = pd.DataFrame(series.values.T, columns=["Rate"])

    rates.insert(0, "Maturity", years)
    if return_date:
        return rates, date_of_yield
    return rates


@log_start_end(log=logger)
@check_api_key(["API_FRED_KEY"])
def get_estr(
    parameter: str = "volume_weighted_trimmed_mean_rate",
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
) -> pd.DataFrame:
    """Obtain data for Euro Short-Term Rate (ESTR)

    Parameters
    ----------
    parameter: str
        The parameter to get data for.
    start_date: Optional[str]
        Start date, formatted YYYY-MM-DD
    end_date: Optional[str]
        End date, formatted YYYY-MM-DD
    """
    series_id = ESTR_PARAMETER_TO_ECB_ID.get(parameter, "")

    return pd.DataFrame(
        get_series_data(series_id=series_id, start_date=start_date, end_date=end_date),
        columns=[series_id],
    )


@log_start_end(log=logger)
@check_api_key(["API_FRED_KEY"])
def get_sofr(
    parameter: str = "overnight",
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
) -> pd.DataFrame:
    """Obtain data for Secured Overnight Financing Rate (SOFR)

    Parameters
    ----------
    parameter: str
        The parameter to get data for. Choose from:
            "overnight"
            "30_day_average"
            "90_day_average"
            "180_day_average"
            "index"
    start_date: Optional[str]
        Start date, formatted YYYY-MM-DD
    end_date: Optional[str]
        End date, formatted YYYY-MM-DD
    """
    series_id = SOFR_PARAMETER_TO_FRED_ID.get(parameter, "")

    return pd.DataFrame(
        get_series_data(series_id=series_id, start_date=start_date, end_date=end_date),
        columns=[series_id],
    )


@log_start_end(log=logger)
@check_api_key(["API_FRED_KEY"])
def get_sonia(
    parameter: str = "rate",
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
) -> pd.DataFrame:
    """Obtain data for Sterling Overnight Index Average (SONIA)

    Parameters
    ----------
    parameter: str
        The parameter to get data for.
    start_date: Optional[str]
        Start date, formatted YYYY-MM-DD
    end_date: Optional[str]
        End date, formatted YYYY-MM-DD
    """
    series_id = SONIA_PARAMETER_TO_FRED_ID.get(parameter, "")

    return pd.DataFrame(
        get_series_data(series_id=series_id, start_date=start_date, end_date=end_date),
        columns=[series_id],
    )


@log_start_end(log=logger)
@check_api_key(["API_FRED_KEY"])
def get_ameribor(
    parameter: str = "overnight",
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
) -> pd.DataFrame:
    """Obtain data for American Interbank Offered Rate (AMERIBOR)

    Parameters
    ----------
    parameter: str
        The parameter to get data for. Choose from:
            "overnight": "AMERIBOR",
            "term_30": "AMBOR30T",
            "term_90": "AMBOR90T",
            "1_week_term_structure": "AMBOR1W",
            "1_month_term_structure": "AMBOR1M",
            "3_month_term_structure": "AMBOR3M",
            "6_month_term_structure": "AMBOR6M",
            "1_year_term_structure": "AMBOR1Y",
            "2_year_term_structure": "AMBOR2Y",
            "30_day_ma": "AMBOR30",
            "90_day_ma": "AMBOR90",
    start_date: Optional[str]
        Start date, formatted YYYY-MM-DD
    end_date: Optional[str]
        End date, formatted YYYY-MM-DD
    """
    series_id = AMERIBOR_PARAMETER_TO_FRED_ID.get(parameter, "")

    return pd.DataFrame(
        get_series_data(series_id=series_id, start_date=start_date, end_date=end_date),
        columns=[series_id],
    )


def get_fed(
    parameter: str = "monthly",
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
    overnight: bool = False,
    quantiles: bool = False,
    target: bool = False,
) -> pd.DataFrame:
    """Obtain data for Effective Federal Funds Rate.

    Parameters
    ----------
    parameter: str
        The parameter to get data for. Choose from:
            "monthly"
            "daily"
            "weekly"
            "daily_excl_weekend"
            "annual"
            "biweekly"
            "volume"
    start_date: Optional[str]
        Start date, formatted YYYY-MM-DD
    end_date: Optional[str]
        End date, formatted YYYY-MM-DD
    overnight: bool
        Whether you want to plot the Overnight Banking Federal Rate
    quantiles: bool
        Whether you want to see the 1, 25, 75 and 99 percentiles
    target: bool
        Whether you want to see the high and low target range
    """
    series_id = FED_PARAMETER_TO_FRED_ID[parameter]

    if overnight:
        # This piece of code adjusts the series id when the user wants to plot the overnight rate
        if series_id == "DFF":
            series_id = "OBFR"
        elif series_id == "EFFRVOL":
            series_id = "OBFRVOL"
        else:
            console.print(
                "The Overnight Banking Federal Rate only supports Daily data."
            )
            series_id = "OBFR"

    if quantiles or target and not overnight:
        data_series = [series_id if series_id != "EFFRVOL" else "EFFR"]
        series_id = series_id if series_id != "EFFRVOL" else "EFFR"

        if quantiles:
            data_series.extend(["EFFR1", "EFFR25", "EFFR75", "EFFR99"])
        if target:
            data_series.extend(["DFEDTARU", "DFEDTARL"])

        df = pd.DataFrame()

        for series in data_series:
            data = pd.DataFrame(
                get_series_data(
                    series_id=series, start_date=start_date, end_date=end_date
                ),
                columns=[series],
            )
            df = data if series == series_id else pd.concat([df, data], axis=1)

        df = df.dropna()
        return df
    return pd.DataFrame(
        get_series_data(series_id=series_id, start_date=start_date, end_date=end_date),
        columns=[series_id],
    )


def get_iorb(
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
) -> pd.DataFrame:
    """Obtain data for Interest Rate on Reserve Balances.

    Parameters
    ----------
    start_date: Optional[str]
        Start date, formatted YYYY-MM-DD
    end_date: Optional[str]
        End date, formatted YYYY-MM-DD
    """
    return pd.DataFrame(
        get_series_data(series_id="IORB", start_date=start_date, end_date=end_date),
        columns=["IORB"],
    )


def get_projection(long_run: bool = False):
    """Obtain data for the Federal Reserve's projection of the federal funds rate.

    Parameters
    ----------
    long_run: str
        Whether to obtain data for the long run projection.
    """
    data_series = {}

    for projection, values in NAME_TO_ID_PROJECTION.items():
        data_series[projection] = get_series_data(series_id=values[long_run])

    data_series_df = pd.DataFrame.from_dict(data_series).dropna()
    data_series_df.index = pd.to_datetime(data_series_df.index).date

    return data_series_df


def get_dwpcr(
    parameter: str = "daily_excl_weekend",
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
) -> pd.DataFrame:
    """Obtain data for the Discount Window Primary Credit Rate.

    Parameters
    ----------
    parameter: str
        The parameter to get data for. Choose from:
            "daily_excl_weekend"
            "monthly"
            "weekly"
            "daily"
            "annual"
    start_date: Optional[str]
        Start date, formatted YYYY-MM-DD
    end_date: Optional[str]
        End date, formatted YYYY-MM-DD
    """
    series_id = DWPCR_PARAMETER_TO_FRED_ID[parameter]
    return pd.DataFrame(
        get_series_data(series_id=series_id, start_date=start_date, end_date=end_date),
        columns=[series_id],
    )


def get_ecb(
    interest_type: Optional[str] = None,
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
) -> pd.DataFrame:
    """Obtain data for ECB interest rates.

    The Governing Council of the ECB sets the key interest rates for the euro area:

    - The interest rate on the main refinancing operations (MRO), which provide
    the bulk of liquidity to the banking system.
    - The rate on the deposit facility, which banks may use to make overnight deposits with the Eurosystem.
    - The rate on the marginal lending facility, which offers overnight credit to banks from the Eurosystem.

    Parameters
    ----------
    interest_type: Optional[str]
        The ability to decide what interest rate to plot. Choose from:
            "deposit"
            "lending"
            "refinancing"
    start_date: Optional[str]
        Start date, formatted YYYY-MM-DD
    end_date: Optional[str]
        End date, formatted YYYY-MM-DD
    """
    if interest_type:
        df = pd.DataFrame(
            get_series_data(
                series_id=NAME_TO_ID_ECB[interest_type],
                start_date=start_date,
                end_date=end_date,
            ),
            columns=[interest_type],
        )

    else:
        series_dictionary = {}

        for interest_name, value in NAME_TO_ID_ECB.items():
            series_dictionary[interest_name.title()] = get_series_data(
                series_id=value, start_date=start_date, end_date=end_date
            )

        df = pd.DataFrame.from_dict(series_dictionary)
        df.index = pd.to_datetime(df.index).date

    return df


def get_usrates(
    parameter: str = "tbills",
    maturity: str = "3_months",
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
):
    """Plot various treasury rates from the United States

    A Treasury Bill (T-Bill) is a short-term U.S. government debt obligation backed by the Treasury Department with a
    maturity of one year or less. Treasury bills are usually sold in denominations of $1,000. However, some can reach
    a maximum denomination of $5 million in non-competitive bids. These securities are widely regarded as low-risk
    and secure investments.

    Yields on Treasury nominal securities at “constant maturity” are interpolated by the U.S. Treasury from the daily
    yield curve for non-inflation-indexed Treasury securities. This curve, which relates the yield on a security to
    its time to maturity, is based on the closing market bid yields on actively traded Treasury securities in the
    over-the-counter market. These market yields are calculated from composites of quotations obtained by the Federal
    Reserve Bank of New York. The constant maturity yield values are read from the yield curve at fixed maturities,
    currently 1, 3, and 6 months and 1, 2, 3, 5, 7, 10, 20, and 30 years. This method provides a yield for a 10-year
    maturity, for example, even if no outstanding security has exactly 10 years remaining to maturity. Similarly,
    yields on inflation-indexed securities at “constant maturity” are interpolated from the daily yield curve for
    Treasury inflation protected securities in the over-the-counter market. The inflation-indexed constant maturity
    yields are read from this yield curve at fixed maturities, currently 5, 7, 10, 20, and 30 years.

    Parameters
    ----------
    parameter: str
        Either "tbills", "cmn", or "tips".
    maturity: str
        Depending on the chosen parameter, a set of maturities is available.
            "4_week": {"tbill": "DTB4WK"},
            "1_month": {"cmn": "DGS1MO"},
            "3_month": {"tbill": "TB3MS", "cmn": "DGS3MO"},
            "6_month": {"tbill": "DTB6", "cmn": "DGS6MO"},
            "1_year": {"tbill": "DTB1YR", "cmn": "DGS1"},
            "2_year": {"cmn": "DGS2"},
            "3_year": {"cmn": "DGS3"},
            "5_year": {"tips": "DFII5", "cmn": "DGS5"},
            "7_year": {"tips": "DFII7", "cmn": "DGS7"},
            "10_year": {"tips": "DFII10", "cmn": "DGS10"},
            "20_year": {"tips": "DFII20", "cmn": "DGS20"},
            "30_year": {"tips": "DFII30", "cmn": "DGS30"},
    start_date: Optional[str]
        Start date, formatted YYYY-MM-DD
    end_date: Optional[str]
        End date, formatted YYYY-MM-DD
    """
    series_id = USARATES_TO_FRED_ID[maturity][parameter]
    return pd.DataFrame(
        get_series_data(series_id=series_id, start_date=start_date, end_date=end_date),
        columns=[series_id],
    )


def get_icebofa(
    data_type: str = "yield",
    category: str = "all",
    area: str = "us",
    grade: str = "non_sovereign",
    options: bool = False,
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
) -> pd.DataFrame:
    """Get data for ICE BofA US Corporate Bond Indices.

    Find the available options by using the options parameter.

    Parameters
    ----------
    data_type: str
        The type of data you want to see, either "yield", "yield_to_worst", "total_return", or "spread"
    category: str
        The type of category you want to see, either "all", "duration", "eur" or "usd".
    area: str
        The type of area you want to see, either "asia", "emea", "eu", "ex_g10", "latin_america" or "us"
    grade: str
        The type of grade you want to see, either "a", "aa", "aaa", "b", "bb", "bbb", "ccc", "crossover",
        "high_grade", "high_yield", "non_financial", "non_sovereign", "private_sector", "public_sector"
    options: bool
        Set to True to obtain the available options.
    start_date: Optional[str]
        Start date, formatted YYYY-MM-DD
    end_date: Optional[str]
        End date, formatted YYYY-MM-DD
    """
    series = pd.read_csv(ice_bofa_path)

    if options:
        return series.drop(
            ["Frequency", "Units", "Asset Class", "FRED Series ID", "Description"],
            axis=1,
        )[
            series["Type"].isin(
                ["yield", "yield_to_worst", "total_return"]
                if data_type != "spread"
                else ["spread"]
            )
        ]

    if data_type == "total_return":
        units = "index"
    elif data_type in ["yield", "yield_to_worst", "spread"]:
        units = "percent"
    else:
        console.print(
            "Please choose either 'yield', 'yield_to_worst', "
            "'total_return' or 'spread'."
        )
        return pd.DataFrame()

    series = series[
        (series["Type"] == data_type)
        & (series["Units"] == units)
        & (series["Frequency"] == "daily")
        & (series["Asset Class"] == "bonds")
        & (series["Category"] == category)
        & (series["Area"] == area)
        & (series["Grade"] == grade)
    ]

    if series.empty:
        console.print("The combination of parameters does not result in any data.")
        return pd.DataFrame()

    series_dictionary = {}

    for series_id, title in series[["FRED Series ID", "Title"]].values:
        series_dictionary[title] = get_series_data(
            series_id=series_id, start_date=start_date, end_date=end_date
        )

    df = pd.DataFrame.from_dict(series_dictionary)
    df.index = pd.to_datetime(df.index).date
    return df


def get_moody(
    data_type: str = "aaa",
    spread: Optional[str] = None,
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
) -> pd.DataFrame:
    """Get data for Moody Corporate Bond Index

    Parameters
    ----------
    data_type: str
        The type of data you want to see, either "aaa" or "baa"
    spread: Optional[str]
        Whether you want to show the spread for treasury or fed_funds
    start_date: Optional[str]
        Start date, formatted YYYY-MM-DD
    end_date: Optional[str]
        End date, formatted YYYY-MM-DD
    """
    series_id = MOODY_TO_OPTIONS["Type"][data_type][spread if spread else "index"]["id"]

    series = get_series_data(
        series_id=series_id, start_date=start_date, end_date=end_date
    )

    df = pd.DataFrame(series, columns=[f"{data_type}_{spread if spread else 'index'}"])
    df.index = pd.to_datetime(df.index).date
    return df


def get_cp(
    maturity: str = "30d",
    category: str = "financial",
    grade: str = "aa",
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
):
    """Obtain Commercial Paper data

    Parameters
    ----------
    maturity: str
        The maturity you want to see, either "overnight", "7d", "15d", "30d", "60d" or "90d"
    category: str
        The category you want to see, either "asset_backed", "financial" or "non_financial"
    grade: str
        The type of grade you want to see, either "a2_p2" or "aa"
    description: bool
        Whether you wish to obtain a description of the data.
    start_date: Optional[str]
        Start date, formatted YYYY-MM-DD
    end_date: Optional[str]
        End date, formatted YYYY-MM-DD
    """
    if grade == "a2_p2" and category != "non_financial":
        category = "non_financial"
    series = pd.read_csv(commercial_paper_path)
    series = series[
        (series["Maturity"] == maturity)
        & (series["Category"] == category)
        & (series["Grade"] == grade)
    ]

    if series.empty:
        console.print("The combination of parameters does not result in any data.")
        return pd.DataFrame()

    series_dictionary = {}

    for series_id, title in series[["FRED Series ID", "Title"]].values:
        series_dictionary[title] = get_series_data(
            series_id=series_id, start_date=start_date, end_date=end_date
        )

    df = pd.DataFrame.from_dict(series_dictionary)
    df.index = pd.to_datetime(df.index).date
    return df


def get_spot(
    maturity: List = ["10y"],
    category: List = ["spot_rate"],
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
) -> pd.DataFrame:
    """
    The spot rate for any maturity is the yield on a bond that provides
    a single payment at that maturity. This is a zero coupon bond. Because each
    spot rate pertains to a single cashflow, it is the relevant interest rate
    concept for discounting a pension liability at the same maturity.

    Parameters
    ----------
    maturity: str
        The maturity you want to see (ranging from '1y' to '100y' in interval of 0.5, e.g. '50.5y')
    category: list
        The category you want to see ('par_yield' and/or 'spot_rate')
    description: bool
        Whether you wish to obtain a description of the data.
    start_date: Optional[str]
        Start date, formatted YYYY-MM-DD
    end_date: Optional[str]
        End date, formatted YYYY-MM-DD
    """
    series = pd.read_csv(spot_rates_path)

    series = series[
        (series["Maturity"].isin(maturity)) & (series["Category"].isin(category))
    ]

    if "par_yield" in category and "par_yield" not in series["Category"].values:
        console.print(
            "No Par Yield data available for (some of) the selected maturities. "
            "Only 2y, 5y, 10y and 30y is available."
        )

    series_dictionary = {}

    for series_id, title in series[["FRED Series ID", "Title"]].values:
        series_dictionary[title] = get_series_data(
            series_id=series_id, start_date=start_date, end_date=end_date
        )

    df = pd.DataFrame.from_dict(series_dictionary)
    df.index = pd.to_datetime(df.index).date
    return df


def get_hqm(
    date: Optional[str] = None,
    par: bool = False,
) -> Tuple[pd.DataFrame, str]:
    """
    The HQM yield curve represents the high quality corporate bond market, i.e.,
    corporate bonds rated AAA, AA, or A.  The HQM curve contains two regression terms. These
    terms are adjustment factors that blend AAA, AA, and A bonds into a single HQM yield curve
    that is the market-weighted average (MWA) quality of high quality bonds.

    Parameters
    ----------
    date: str
        The date of the yield curve you wish to plot
    par: bool
        Whether you wish to plot the par yield curve as well
    """
    df = pd.DataFrame()
    data_types = ["spot", "par"] if par else ["spot"]

    for types in data_types:
        subset, date_of_yield = get_yield_curve(date, True, spot_or_par=types)
        subset.set_index("Maturity", inplace=True)
        subset.columns = [types]

        df = pd.concat([df, subset], axis=1)

    if par:
        # Drop NaNs because of length mismatch
        df = df.dropna()

    if df.empty:
        console.print(f"[red]Yield data not found at {date_of_yield}.[/red]\n")

    return df, date_of_yield


def get_tbffr(
    parameter: str = "3_month",
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
) -> pd.DataFrame:
    """Get data for Selected Treasury Bill Minus Federal Funds Rate.

    Parameters
    ----------
    parameter: str
        FRED ID of TBFFR data to plot, options: ["3_month", "6_month"]
    start_date: Optional[str]
        Start date, formatted YYYY-MM-DD
    end_date: Optional[str]
        End date, formatted YYYY-MM-DD
    """
    series_id = TBFFR_PARAMETER_TO_FRED_ID[parameter]

    return pd.DataFrame(
        get_series_data(series_id, start_date, end_date), columns=[series_id]
    )


def get_icespread(
    category: str = "all",
    area: str = "us",
    grade: str = "non_sovereign",
    options: bool = False,
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
) -> pd.DataFrame:
    """Get data for ICE BofA US Corporate Bond Spreads

    Parameters
    ----------
    category: str
        The type of category you want to see, either "all", "duration", "eur" or "usd".
    area: str
        The type of area you want to see, either "asia", "emea", "eu", "ex_g10", "latin_america" or "us"
    grade: str
        The type of grade you want to see, either "a", "aa", "aaa", "b", "bb", "bbb", "ccc", "crossover",
        "high_grade", "high_yield", "non_financial", "non_sovereign", "private_sector", "public_sector"
    """
    return get_icebofa(
        data_type="spread",
        category=category,
        area=area,
        grade=grade,
        options=options,
        start_date=start_date,
        end_date=end_date,
    )


def get_ffrmc(
    parameter: str = "10_year",
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
) -> pd.DataFrame:
    """Get data for Selected Treasury Constant Maturity Minus Federal Funds Rate

    Constant maturity is the theoretical value of a U.S. Treasury that is based on recent values of auctioned U.S.
    Treasuries. The value is obtained by the U.S. Treasury on a daily basis through interpolation of the Treasury
    yield curve which, in turn, is based on closing bid-yields of actively-traded Treasury securities.

    Parameters
    ----------
    parameter: str
        FRED ID of FFRMC data to plot
    start_date: Optional[str]
        Start date, formatted YYYY-MM-DD
    end_date: Optional[str]
        End date, formatted YYYY-MM-DD
    """
    series_id = FFRMC_PARAMETER_TO_FRED_ID[parameter]
    return pd.DataFrame(
        get_series_data(series_id, start_date, end_date), columns=[series_id]
    )


def get_tmc(
    parameter: str = "3_month",
    start_date: Optional[str] = None,
    end_date: Optional[str] = None,
) -> pd.DataFrame:
    """Get data for 10-Year Treasury Constant Maturity Minus Selected Treasury Constant Maturity.

    Constant maturity is the theoretical value of a U.S. Treasury that is based on recent values of auctioned U.S.
    Treasuries. The value is obtained by the U.S. Treasury on a daily basis through interpolation of the Treasury
    yield curve which, in turn, is based on closing bid-yields of actively-traded Treasury securities.

    Parameters
    ----------
    parameter: str
        FRED ID of TMC data to plot, options: ["T10Y3M", "T10Y3M"]
    start_date: Optional[str]
        Start date, formatted YYYY-MM-DD
    end_date: Optional[str]
        End date, formatted YYYY-MM-DD
    """
    series_id = TMC_PARAMETER_TO_FRED_ID[parameter]
    return pd.DataFrame(
        get_series_data(series_id, start_date, end_date), columns=[series_id]
    )
