"""Alpha Vantage Model"""
__docformat__ = "numpy"

import logging
from typing import Dict, List

import numpy as np
import pandas as pd
import requests
from alpha_vantage.fundamentaldata import FundamentalData
from openbb_terminal import config_terminal as cfg
from openbb_terminal.decorators import log_start_end
from openbb_terminal.helper_funcs import lambda_long_number_format
from openbb_terminal.rich_config import console
from openbb_terminal.stocks.fundamental_analysis import yahoo_finance_model
from openbb_terminal.stocks.stocks_helper import clean_fraction
from openbb_terminal.stocks.fundamental_analysis.fa_helper import clean_df_index

logger = logging.getLogger(__name__)


def check_premium_key(json_response: Dict) -> bool:
    """Checks if the response is the premium endpoint"""
    if json_response == {
        "Information": "Thank you for using Alpha Vantage! This is a premium endpoint. You may subscribe to "
        "any of the premium plans at https://www.alphavantage.co/premium/ to instantly unlock all premium endpoints"
    }:
        console.print(
            "This is a premium endpoint for AlphaVantage. Please use a premium key.\n"
        )
        return True
    return False


@log_start_end(log=logger)
def get_overview(symbol: str) -> pd.DataFrame:
    """Get alpha vantage company overview

    Parameters
    ----------
    symbol : str
        Stock ticker symbol

    Returns
    -------
    pd.DataFrame
        Dataframe of fundamentals
    """
    # Request OVERVIEW data from Alpha Vantage API
    s_req = f"https://www.alphavantage.co/query?function=OVERVIEW&symbol={symbol}&apikey={cfg.API_KEY_ALPHAVANTAGE}"
    result = requests.get(s_req, stream=True)
    result_json = result.json()

    df_fa = pd.DataFrame()

    # If the returned data was unsuccessful
    if "Error Message" in result_json:
        console.print(result_json["Error Message"])
    else:
        # check if json is empty
        if not result_json:
            console.print("No data found from Alpha Vantage\n")
        # Parse json data to dataframe
        elif "Note" in result_json:
            console.print(result_json["Note"], "\n")
        else:
            df_fa = pd.json_normalize(result_json)

            # Keep json data sorting in dataframe
            df_fa = df_fa[list(result_json.keys())].T
            df_fa.iloc[5:] = df_fa.iloc[5:].applymap(
                lambda x: lambda_long_number_format(x)
            )
            clean_df_index(df_fa)
            df_fa = df_fa.rename(
                index={
                    "E b i t d a": "EBITDA",
                    "P e ratio": "PE ratio",
                    "P e g ratio": "PEG ratio",
                    "E p s": "EPS",
                    "Revenue per share t t m": "Revenue per share TTM",
                    "Operating margin t t m": "Operating margin TTM",
                    "Return on assets t t m": "Return on assets TTM",
                    "Return on equity t t m": "Return on equity TTM",
                    "Revenue t t m": "Revenue TTM",
                    "Gross profit t t m": "Gross profit TTM",
                    "Diluted e p s t t m": "Diluted EPS TTM",
                    "Quarterly earnings growth y o y": "Quarterly earnings growth YOY",
                    "Quarterly revenue growth y o y": "Quarterly revenue growth YOY",
                    "Trailing p e": "Trailing PE",
                    "Forward p e": "Forward PE",
                    "Price to sales ratio t t m": "Price to sales ratio TTM",
                    "E v to revenue": "EV to revenue",
                    "E v to e b i t d a": "EV to EBITDA",
                }
            )
    return df_fa


@log_start_end(log=logger)
def get_key_metrics(symbol: str) -> pd.DataFrame:
    """Get key metrics from overview

    Parameters
    ----------
    symbol : str
        Stock ticker symbol

    Returns
    -------
    pd.DataFrame
        Dataframe of key metrics
    """
    # Request OVERVIEW data
    s_req = f"https://www.alphavantage.co/query?function=OVERVIEW&symbol={symbol}&apikey={cfg.API_KEY_ALPHAVANTAGE}"
    result = requests.get(s_req, stream=True)
    result_json = result.json()

    # If the returned data was unsuccessful
    if "Error Message" in result_json:
        console.print(result_json["Error Message"])
    else:
        # check if json is empty
        if not result_json or len(result_json) < 2:
            console.print("No data found from Alpha Vantage\n")
            return pd.DataFrame()

        df_fa = pd.json_normalize(result_json)
        df_fa = df_fa[list(result_json.keys())].T
        df_fa = df_fa.applymap(lambda x: lambda_long_number_format(x))
        clean_df_index(df_fa)
        df_fa = df_fa.rename(
            index={
                "E b i t d a": "EBITDA",
                "P e ratio": "PE ratio",
                "P e g ratio": "PEG ratio",
                "E p s": "EPS",
                "Return on equity t t m": "Return on equity TTM",
                "Price to sales ratio t t m": "Price to sales ratio TTM",
            }
        )
        as_key_metrics = [
            "Market capitalization",
            "EBITDA",
            "EPS",
            "PE ratio",
            "PEG ratio",
            "Price to book ratio",
            "Return on equity TTM",
            "Price to sales ratio TTM",
            "Dividend yield",
            "50 day moving average",
            "Analyst target price",
            "Beta",
        ]
        return df_fa.loc[as_key_metrics]

    return pd.DataFrame()


@log_start_end(log=logger)
def get_income_statements(
    symbol: str,
    limit: int = 5,
    quarterly: bool = False,
    ratios: bool = False,
    plot: bool = False,
) -> pd.DataFrame:
    """Get income statements for company

    Parameters
    ----------
    symbol : str
        Stock ticker symbol
    limit : int
        Number of past to get
    quarterly : bool, optional
        Flag to get quarterly instead of annual, by default False
    ratios: bool
        Shows percentage change, by default False
    plot: bool
        If the data shall be formatted ready to plot

    Returns
    -------
    pd.DataFrame
        DataFrame of income statements
    """
    url = (
        f"https://www.alphavantage.co/query?function=INCOME_STATEMENT&symbol={symbol}"
        f"&apikey={cfg.API_KEY_ALPHAVANTAGE}"
    )
    r = requests.get(url)
    response_json = r.json()
    if check_premium_key(response_json):
        return pd.DataFrame()

    # If the returned data was unsuccessful
    if "Error Message" in response_json:
        console.print(response_json["Error Message"])
    else:
        # check if json is empty
        if not response_json:
            console.print(
                "No data found from Alpha Vantage, looking in Yahoo Finance\n"
            )
            if (
                yahoo_finance_model.get_financials(symbol, statement="financials")
                is not None
            ):
                return yahoo_finance_model.get_financials(
                    symbol, statement="financials"
                )
        else:
            statements = response_json
            df_fa = pd.DataFrame()

            if quarterly:
                if "quarterlyReports" in statements:
                    df_fa = pd.DataFrame(statements["quarterlyReports"])
            else:
                if "annualReports" in statements:
                    df_fa = pd.DataFrame(statements["annualReports"])

            if df_fa.empty:
                console.print("No data found from Alpha Vantage\n")
                return pd.DataFrame()

            df_fa = df_fa.set_index("fiscalDateEnding")
            df_fa = df_fa[::-1].T

            df_fa = df_fa.replace("None", "0")
            df_fa.iloc[1:] = df_fa.iloc[1:].astype("float")

            df_fa_c = df_fa.iloc[:, -limit:].applymap(
                lambda x: lambda_long_number_format(x)
            )

            if ratios:
                df_fa_pc = df_fa.iloc[1:].pct_change(axis="columns").fillna(0)
                j = 0
                for i in list(range(1, 25)):
                    df_fa.iloc[i] = df_fa_pc.iloc[j]
                    j += 1

            df_fa = df_fa.iloc[:, 0:limit]

            return df_fa_c if not plot else df_fa
    return pd.DataFrame()


@log_start_end(log=logger)
def get_balance_sheet(
    symbol: str,
    limit: int = 5,
    quarterly: bool = False,
    ratios: bool = False,
    plot: bool = False,
) -> pd.DataFrame:
    """Get balance sheets for company

    Parameters
    ----------
    symbol : str
        Stock ticker symbol
    limit : int
        Number of past to get
    quarterly : bool, optional
        Flag to get quarterly instead of annual, by default False
    ratios: bool
        Shows percentage change, by default False
    plot: bool
        If the data shall be formatted ready to plot

    Returns
    -------
    pd.DataFrame
        DataFrame of the balance sheet
    """
    url = f"https://www.alphavantage.co/query?function=BALANCE_SHEET&symbol={symbol}&apikey={cfg.API_KEY_ALPHAVANTAGE}"
    r = requests.get(url)
    response_json = r.json()
    if check_premium_key(response_json):
        return pd.DataFrame()
    # If the returned data was unsuccessful
    if "Error Message" in response_json:
        console.print(response_json["Error Message"])

    # check if json is empty
    if not response_json:
        console.print("No data found from Alpha Vantage, looking in Yahoo Finance\n")
        if (
            yahoo_finance_model.get_financials(symbol, statement="balance-sheet")
            is not None
        ):
            return yahoo_finance_model.get_financials(symbol, statement="balance-sheet")
    else:
        statements = response_json
        df_fa = pd.DataFrame()

        if quarterly:
            if "quarterlyReports" in statements:
                df_fa = pd.DataFrame(statements["quarterlyReports"])
        else:
            if "annualReports" in statements:
                df_fa = pd.DataFrame(statements["annualReports"])

        if df_fa.empty:
            console.print("No data found from Alpha Vantage\n")
            return pd.DataFrame()

        df_fa = df_fa.set_index("fiscalDateEnding")
        df_fa = df_fa[::-1].T

        df_fa = df_fa.replace("None", "0")
        df_fa.iloc[1:] = df_fa.iloc[1:].astype("float")

        df_fa_c = df_fa.iloc[:, -limit:].applymap(
            lambda x: lambda_long_number_format(x)
        )

        if ratios:
            df_fa_pc = df_fa.iloc[1:].pct_change(axis="columns").fillna(0)
            j = 0
            for i in list(range(1, 37)):
                df_fa.iloc[i] = df_fa_pc.iloc[j]
                j += 1

            df_fa_c = df_fa.iloc[:, 0:limit].applymap(
                lambda x: lambda_long_number_format(x)
            )

        df_fa = df_fa.iloc[:, 0:limit]

        return df_fa_c if not plot else df_fa
    return pd.DataFrame()


@log_start_end(log=logger)
def get_cash_flow(
    symbol: str,
    limit: int = 5,
    quarterly: bool = False,
    ratios: bool = False,
    plot: bool = False,
) -> pd.DataFrame:
    """Get cash flows for company

    Parameters
    ----------
    symbol : str
        Stock ticker symbol
    limit : int
        Number of past to get
    quarterly : bool, optional
        Flag to get quarterly instead of annual, by default False
    ratios: bool
        Shows percentage change, by default False
    plot: bool
        If the data shall be formatted ready to plot

    Returns
    -------
    pd.DataFrame
        Dataframe of cash flow statements
    """
    url = f"https://www.alphavantage.co/query?function=CASH_FLOW&symbol={symbol}&apikey={cfg.API_KEY_ALPHAVANTAGE}"
    r = requests.get(url)
    response_json = r.json()
    if check_premium_key(response_json):
        return pd.DataFrame()
    # If the returned data was unsuccessful
    if "Error Message" in response_json:
        console.print(response_json["Error Message"])
    else:
        # check if json is empty
        if not response_json:
            console.print(
                "No data found from Alpha Vantage, looking in Yahoo Finance\n"
            )

            if (
                yahoo_finance_model.get_financials(symbol, statement="cash-flow")
                is not None
            ):
                return yahoo_finance_model.get_financials(symbol, statement="cash-flow")
        else:
            statements = response_json
            df_fa = pd.DataFrame()

            if quarterly:
                if "quarterlyReports" in statements:
                    df_fa = pd.DataFrame(statements["quarterlyReports"])
            else:
                if "annualReports" in statements:
                    df_fa = pd.DataFrame(statements["annualReports"])

            if df_fa.empty:
                console.print("No data found from Alpha Vantage\n")
                return pd.DataFrame()

            df_fa = df_fa.set_index("fiscalDateEnding")
            df_fa = df_fa[::-1].T

            df_fa = df_fa.replace("None", "0")
            df_fa.iloc[1:] = df_fa.iloc[1:].astype("float")

            df_fa_c = df_fa.iloc[:, -limit:].applymap(
                lambda x: lambda_long_number_format(x)
            )

            if ratios:
                df_fa_pc = df_fa.iloc[1:].pct_change(axis="columns").fillna(0)
                j = 0
                for i in list(range(1, 37)):
                    df_fa.iloc[i] = df_fa_pc.iloc[j]
                    j += 1

                df_fa_c = df_fa.iloc[:, 0:limit].applymap(
                    lambda x: lambda_long_number_format(x)
                )

            df_fa = df_fa.iloc[:, 0:limit]

            return df_fa_c if not plot else df_fa
    return pd.DataFrame()


@log_start_end(log=logger)
def get_earnings(symbol: str, quarterly: bool = False) -> pd.DataFrame:
    """Get earnings calendar for ticker

    Parameters
    ----------
    symbol : str
        Stock ticker symbol
    quarterly : bool, optional
        Flag to get quarterly and not annual, by default False

    Returns
    -------
    pd.DataFrame
        Dataframe of earnings
    """
    # Request EARNINGS data from Alpha Vantage API
    s_req = (
        "https://www.alphavantage.co/query?function=EARNINGS&"
        f"symbol={symbol}&apikey={cfg.API_KEY_ALPHAVANTAGE}"
    )
    result = requests.get(s_req, stream=True)
    result_json = result.json()
    df_fa = pd.DataFrame()

    # If the returned data was unsuccessful
    if "Error Message" in result_json:
        console.print(result_json["Error Message"])
    else:
        # check if json is empty
        if not result_json or len(result_json) < 2:
            console.print("No data found from Alpha Vantage\n")
        else:

            df_fa = pd.json_normalize(result_json)

            if quarterly:
                df_fa = pd.DataFrame(df_fa["quarterlyEarnings"][0])
                df_fa = df_fa[
                    [
                        "fiscalDateEnding",
                        "reportedDate",
                        "reportedEPS",
                        "estimatedEPS",
                        "surprise",
                        "surprisePercentage",
                    ]
                ]
                df_fa = df_fa.rename(
                    columns={
                        "fiscalDateEnding": "Fiscal Date Ending",
                        "reportedEPS": "Reported EPS",
                        "estimatedEPS": "Estimated EPS",
                        "reportedDate": "Reported Date",
                        "surprise": "Surprise",
                        "surprisePercentage": "Surprise Percentage",
                    }
                )
            else:
                df_fa = pd.DataFrame(df_fa["annualEarnings"][0])
                df_fa = df_fa.rename(
                    columns={
                        "fiscalDateEnding": "Fiscal Date Ending",
                        "reportedEPS": "Reported EPS",
                    }
                )

    return df_fa


@log_start_end(log=logger)
def df_values(
    df: pd.DataFrame, item: str, index: int = 0, length: int = 2
) -> List[int]:
    """Clean the values from the df

    Parameters
    ----------
    df : pd.DataFrame
        The Dataframe to use
    item : str
        The item to select
    index : int
        The number of row to display
    length : int
        The number of rows to return

    Returns
    -------
    values : List[int]
        The values for the dataframe
    """
    if index:
        df = df.iloc[index : index + length]
    selection = df[item]
    values = selection.apply(lambda x: 0 if (not x or x == "None") else int(x)).values
    return values.tolist()


def replace_df(name: str, row: pd.Series) -> pd.Series:
    """Replaces text in pandas row based on color functions

    Return
    ----------
    name : str
        The name of the row
    row : pd.Series
        The original row

    Parameters
    ----------
    new_row : pd.Series
        The new formatted row
    """
    for i, item in enumerate(row):
        if name == "Mscore":
            row[i] = color_mscore(item)
        elif name in ["Zscore", "McKee"]:
            row[i] = color_zscore_mckee(item)
        else:
            row[i] = str(round(float(item), 2))

    return row


def color_mscore(value: str) -> str:
    """Adds color to mscore dataframe values

    Parameters
    ----------
    value : str
        The string value

    Returns
    -------
    new_value : str
        The string formatted with rich color
    """
    value_float = float(value)
    if value_float <= -2.22:
        return f"[green]{value_float:.2f}[/green]"
    if value_float <= -1.78:
        return f"[yellow]{value_float:.2f}[/yellow]"
    return f"[red]{value_float:.2f}[/red]"


def color_zscore_mckee(value: str) -> str:
    """Adds color to mckee or zscore dataframe values
    Parameters
    ----------
    value : str
        The string value

    Returns
    -------
    new_value : str
        The string formatted with rich color
    """
    value_float = float(value)
    if value_float < 0.5:
        return f"[red]{value_float:.2f}[/red]"
    return f"[green]{value_float:.2f}[/green]"


@log_start_end(log=logger)
def get_fraud_ratios(symbol: str, detail: bool = False) -> pd.DataFrame:
    """Get fraud ratios based on fundamentals

    Parameters
    ----------
    symbol : str
        Stock ticker symbol
    detail : bool
        Whether to provide extra m-score details

    Returns
    -------
    metrics : pd.DataFrame
        The fraud ratios
    """

    try:
        fd = FundamentalData(key=cfg.API_KEY_ALPHAVANTAGE, output_format="pandas")
        # pylint: disable=unbalanced-tuple-unpacking
        df_cf, _ = fd.get_cash_flow_annual(symbol=symbol)
        df_bs, _ = fd.get_balance_sheet_annual(symbol=symbol)
        df_is, _ = fd.get_income_statement_annual(symbol=symbol)

    except ValueError as e:
        if "premium endpoint" in str(e):
            console.print(
                "This is a premium endpoint for AlphaVantage. Please use a premium key.\n"
            )
        return pd.DataFrame()

    # pylint: disable=no-member
    df_cf = df_cf.set_index("fiscalDateEnding")
    df_bs = df_bs.set_index("fiscalDateEnding")
    df_is = df_is.set_index("fiscalDateEnding")
    fraud_years = pd.DataFrame()
    for i in range(len(df_cf) - 1):
        ar = df_values(df_bs, "currentNetReceivables", i)
        sales = df_values(df_is, "totalRevenue", i)
        cogs = df_values(df_is, "costofGoodsAndServicesSold", i)
        ni = df_values(df_is, "netIncome", i)
        ca = df_values(df_bs, "totalCurrentAssets", i)
        cl = df_values(df_bs, "totalCurrentLiabilities", i)
        ppe = df_values(df_bs, "propertyPlantEquipment", i)
        cash = df_values(df_bs, "cashAndCashEquivalentsAtCarryingValue", i)
        cash_and_sec = df_values(df_bs, "cashAndShortTermInvestments", i)
        sec = [y - x for (x, y) in zip(cash, cash_and_sec)]
        ta = df_values(df_bs, "totalAssets", i)
        dep = df_values(df_bs, "accumulatedDepreciationAmortizationPPE", i)
        sga = df_values(df_is, "sellingGeneralAndAdministrative", i)
        tl = df_values(df_bs, "totalLiabilities", i)
        icfo = df_values(df_is, "netIncomeFromContinuingOperations", i)
        cfo = df_values(df_cf, "operatingCashflow", i)

        ratios: Dict = {}
        try:
            ratios["DSRI"] = (ar[0] / sales[0]) / (ar[1] / sales[1])
            ratios["GMI"] = ((sales[1] - cogs[1]) / sales[1]) / (
                (sales[0] - cogs[0]) / sales[0]
            )
            ratios["AQI"] = (1 - ((ca[0] + ppe[0] + sec[0]) / ta[0])) / (
                1 - ((ca[1] + ppe[1] + sec[1]) / ta[1])
            )
            ratios["SGI"] = sales[0] / sales[1]
            ratios["DEPI"] = (dep[1] / (ppe[1] + dep[1])) / (dep[0] / (ppe[0] + dep[0]))
            ratios["SGAI"] = (sga[0] / sales[0]) / (sga[1] / sales[1])
            ratios["LVGI"] = (tl[0] / ta[0]) / (tl[1] / ta[1])
            ratios["TATA"] = (icfo[0] - cfo[0]) / ta[0]
            ratios["Mscore"] = (
                -4.84
                + (0.92 * ratios["DSRI"])
                + (0.58 * ratios["GMI"])
                + (0.404 * ratios["AQI"])
                + (0.892 * ratios["SGI"])
                + (0.115 * ratios["DEPI"] - (0.172 * ratios["SGAI"]))
                + (4.679 * ratios["TATA"])
                - (0.327 * ratios["LVGI"])
            )

            zscore = (
                -4.336
                - (4.513 * (ni[0] / ta[0]))
                + (5.679 * (tl[0] / ta[0]))
                + (0.004 * (ca[0] / cl[0]))
            )
            v1 = np.log(ta[0] / 1000)
            v2 = ni[0] / ta[0]
            v3 = cash[0] / cl[0]

            x = ((v1 + 0.85) * v2) - 0.85
            y = 1 + v3

            mckee = x**2 / (x**2 + y**2)
            ratios["Zscore"] = zscore
            ratios["McKee"] = mckee
        except ZeroDivisionError:
            for item in [
                "DSRI",
                "GMI",
                "AQI",
                "SGI",
                "DEPI",
                "SGAI",
                "LVGI",
                "TATA",
                "Mscore",
                "Zscore",
                "Mckee",
            ]:
                ratios[item] = "N/A"
        if fraud_years.empty:
            fraud_years.index = ratios.keys()
        fraud_years[df_cf.index[i]] = ratios.values()
    fraud_years = fraud_years[sorted(fraud_years)]
    if not detail:
        details = ["DSRI", "GMI", "AQI", "SGI", "DEPI", "SGAI", "LVGI", "TATA"]
        fraud_years = fraud_years.drop(details)

    return fraud_years


@log_start_end(log=logger)
def get_dupont(symbol: str) -> pd.DataFrame:
    """Get dupont ratios

    Parameters
    ----------
    symbol : str
        Stock ticker symbol

    Returns
    -------
    dupont : pd.DataFrame
        The dupont ratio breakdown
    """

    try:
        fd = FundamentalData(key=cfg.API_KEY_ALPHAVANTAGE, output_format="pandas")
        # pylint: disable=unbalanced-tuple-unpacking
        df_bs, _ = fd.get_balance_sheet_annual(symbol=symbol)
        df_is, _ = fd.get_income_statement_annual(symbol=symbol)

    except ValueError as e:
        if "premium endpoint" in str(e):
            console.print(
                "This is a premium endpoint for AlphaVantage. Please use a premium key.\n"
            )
        return pd.DataFrame()

    # pylint: disable=no-member
    df_bs = df_bs.set_index("fiscalDateEnding")
    df_is = df_is.set_index("fiscalDateEnding")
    dupont_years = pd.DataFrame()

    for i in range(len(df_bs)):
        ni = df_values(df_is, "netIncome", i, 1)
        pretax = df_values(df_is, "incomeBeforeTax", i, 1)
        ebit = df_values(df_is, "ebit", i, 1)
        sales = df_values(df_is, "totalRevenue", i, 1)
        assets = df_values(df_bs, "totalAssets", i, 1)
        equity = df_values(df_bs, "totalShareholderEquity", i, 1)

        ratios: Dict = {}
        try:
            ratios["Tax Burden"] = clean_fraction(ni[0], pretax[0])
            ratios["Interest Burden"] = clean_fraction(pretax[0], ebit[0])
            ratios["EBIT Margin"] = clean_fraction(ebit[0], sales[0])
            ratios["Asset Turnover"] = clean_fraction(sales[0], assets[0])
            ratios["Finance Leverage"] = clean_fraction(assets[0], equity[0])
            ratios["ROI"] = clean_fraction(ni[0], equity[0])
        except IndexError:
            pass

        if dupont_years.empty:
            dupont_years.index = ratios.keys()
        dupont_years[df_bs.index[i]] = ratios.values()
    dupont_years = dupont_years[sorted(dupont_years)]
    return dupont_years
