"Combine the financial model components to create a complete model."

import sys

sys.path.append(r".")

import os

import numpy as np
import pandas as pd
import requests
from dotenv import load_dotenv

from services.fin_modeling import utils as fin_utils
from services.fin_modeling import wacc

load_dotenv()


class DCFModel:
    "Combine the financial model components to create a complete model."

    alpha_vantage_key = os.getenv("Alpha_Vantage")

    def __init__(self, company_ticker: str) -> None:
        self.company_ticker = company_ticker

        # set the model assumptions
        self.model_assumptions()

        self.wacc = wacc.WACC(0.1).calculate_wacc()

    def calculate_dcf(self):
        "This method will calculate the DCF for a company"
        # get the historical data
        self.get_historical_data()
        # calculate the model assumptions
        self.calculate_model_historical_ratios(self.df_fin_inputs)

        self.forecast_cash_flows = self.calculate_cash_flows()
        self.calculate_pv_of_cash_flows()
        self.calculate_terminal_value()
        self.calculate_enterprise_value()
        self.calculate_equity_value()

    def model_assumptions(self):
        "This method will calculate the model assumptions for a company"
        # TODO: get the model assumptions from the user or from other information
        # make them dynamic to the number of years
        self.model_forecast_years = 10
        self.revenue_growth_T = 0.07
        self.ebit_perc_T = 0.23
        self.tax_perc_T = 0.21
        self.dna_perc_T = 0.03
        self.capex_perc_T = 0.05
        self.nwc_perc_T = 0.05
        self.TGR = 0.025

    def calculate_enterprise_value(self):
        "This method will calculate the DCF for a company"
        self.ev = self.forecast_cash_flows["pv_FCF"].sum() + self.pv_TV
        pass

    def calculate_equity_value(self):
        "This method will calculate the equity value for a company"
        self.equity_value = (
            self.ev
            - self.model_fins_last_year["totalDebt"]
            + self.model_fins_last_year["cashAndShortTermInvestments"]
        )
        pass

    def calculate_terminal_value(self):
        "This method will calculate the terminal value for a company"
        # get the terminal value
        # TODO: Add multiple terminal value calculations
        self.terminal_values = (
            self.forecast_cash_flows["freeCashFlow"].values[-1]
            * (1 + self.TGR)
            / (self.wacc - self.TGR)
        )
        self.pv_TV = self.terminal_values / (1 + self.wacc) ** self.model_forecast_years
        pass

    def calculate_pv_of_cash_flows(self):
        "This method will calculate the present value of cash flows for a company"

        def calculate_present_value(cash_flows, discount_rate):
            # Calculate the present value using: PV = CF / (1 + r)^t + TV/(1 + r)^T
            present_values_cf = [
                cf / (1 + discount_rate) ** t
                for t, cf in enumerate(cash_flows, start=1)
            ]
            return present_values_cf

        self.forecast_cash_flows["pv_FCF"] = calculate_present_value(
            self.forecast_cash_flows["freeCashFlow"].values, self.wacc
        )

    def calculate_cash_flows(self):
        "This method will calculate the cash flows for a company"
        # get the interpolation class
        # TODO: add the ability to choose the interpolation method
        interp_cl = fin_utils.Interpolation()

        years = range(
            self.df_fin_inputs.index[-1] + 1,
            self.df_fin_inputs.index[-1] + self.model_forecast_years + 1,
        )
        df_proj = pd.DataFrame(index=years, columns=self.df_fin_inputs.columns)
        # linear interpolation
        n = self.model_forecast_years
        df_proj["rev_growth"] = interp_cl.linear(
            self.model_fins_last_year["rev_growth"], self.revenue_growth_T, n
        )
        df_proj["ebit_of_sales"] = interp_cl.linear(
            self.model_fins_last_year["ebit_of_sales"], self.ebit_perc_T, n
        )
        df_proj["dna_of_sales"] = interp_cl.linear(
            self.model_fins_last_year["dna_of_sales"], self.dna_perc_T, n
        )
        df_proj["capex_of_sales"] = interp_cl.linear(
            self.model_fins_last_year["capex_of_sales"], self.capex_perc_T, n
        )
        df_proj["tax_of_ebit"] = interp_cl.linear(
            self.model_fins_last_year["tax_of_ebit"], self.tax_perc_T, n
        )
        df_proj["nwc_of_sales"] = interp_cl.linear(
            self.model_fins_last_year["nwc_of_sales"], self.nwc_perc_T, n
        )
        # cumulative values
        df_proj["totalRevenue"] = (
            self.model_fins_last_year["totalRevenue"]
            * (1 + df_proj["rev_growth"]).cumprod()
        )
        df_proj["ebit"] = (
            self.model_fins_last_year["ebit"] * (1 + df_proj["ebit_of_sales"]).cumprod()
        )
        df_proj["capitalExpenditures"] = (
            self.model_fins_last_year["capitalExpenditures"]
            * (1 + df_proj["capex_of_sales"]).cumprod()
        )
        df_proj["depreciationAndAmortization"] = (
            self.model_fins_last_year["depreciationAndAmortization"]
            * (1 + df_proj["dna_of_sales"]).cumprod()
        )
        df_proj["delta_nwc"] = (
            self.model_fins_last_year["delta_nwc"]
            * (1 + df_proj["nwc_of_sales"]).cumprod()
        )
        df_proj["taxProvision"] = (
            self.model_fins_last_year["tax_of_ebit"] * (df_proj["ebit"]).cumprod()
        )
        df_proj["ebiat"] = df_proj["ebit"] - df_proj["taxProvision"]

        # calculate free cash flow
        df_proj["freeCashFlow"] = (
            df_proj["ebiat"]
            + df_proj["depreciationAndAmortization"]
            - df_proj["capitalExpenditures"]
            - df_proj["delta_nwc"]
        )

        return df_proj

    def get_historical_data(self):
        """This method will get the historical data for a company.
        For public companies, use AlphaVantage API to get the historical data.
        """
        # download BS, IS, CFS from EOD historical data
        # use alpha vantage

        IncomeStat = self.get_income_statement()
        BalanceSheet = self.get_balance_sheet()
        CashFlowStat = self.get_cash_flow_statement()

        # transpose and concatenate
        df_bal = pd.DataFrame(BalanceSheet)
        df_bal.set_index("fiscalDateEnding", inplace=True)
        df_inc = pd.DataFrame(IncomeStat)
        df_inc.set_index("fiscalDateEnding", inplace=True)
        df_cfs = pd.DataFrame(CashFlowStat)
        df_cfs.set_index("fiscalDateEnding", inplace=True)
        df_all = pd.concat([df_bal, df_inc, df_cfs], axis=1).sort_index()
        self.df_fin_inputs = df_all.loc[
            :, ~df_all.columns.duplicated()
        ]  # remove duplicated columns
        self.df_fin_inputs = self.clean_df(self.df_fin_inputs)
        pass

    def clean_df(self, df):
        "This method will clean the data for the company"
        df = df.iloc[
            :,
            1:,
        ].applymap(lambda x: float(x) if x is not None and x != "None" else np.NaN)
        df.index = pd.to_datetime(df.index)

        # TODO: convert the df to calendar year or fiscal year based on the input
        df.index = df.index.year

        # TODO add the ability to dynamics choose the format base
        df = df.applymap(fin_utils.format_value_millions)
        return df

    def calculate_model_historical_ratios(self, df):
        "This method will calculate the model assumptions for a company"

        # calculate net working capital
        self.calculate_nwc(df)

        df["rev_growth"] = df["totalRevenue"].pct_change()
        df["delta_nwc"] = df["netWorkingCapital"].diff()
        df["ebit_of_sales"] = df["ebit"] / df["totalRevenue"]
        df["dna_of_sales"] = df["depreciationAndAmortization"] / df["totalRevenue"]
        df["capex_of_sales"] = df["capitalExpenditures"] / df["totalRevenue"]
        df["nwc_of_sales"] = df["delta_nwc"] / df["totalRevenue"]

        # TODO: get the tax rate from the SEC filings
        df["tax_of_ebit"] = df["incomeTaxExpense"] / df["ebit"]
        df["ebiat"] = df["ebit"] - df["incomeTaxExpense"]

        self.model_fins_last_year = df.iloc[-1, :]

        return df

    def get_income_statement(self):
        "This method will get the income statement for a company"

        IncomeStat = requests.get(
            f"https://www.alphavantage.co/query?function=INCOME_STATEMENT&symbol={self.company_ticker}&apikey={self.alpha_vantage_key}"
        ).json()

        return IncomeStat["annualReports"]

    def get_balance_sheet(self):
        "This method will get the balance sheet for a company"

        BalanceSheet = requests.get(
            f"https://www.alphavantage.co/query?function=BALANCE_SHEET&symbol={self.company_ticker}&apikey={self.alpha_vantage_key}"
        ).json()

        return BalanceSheet["annualReports"]

    def get_cash_flow_statement(self):
        "This method will get the cash flow statement for a company"

        CashFlowStat = requests.get(
            f"https://www.alphavantage.co/query?function=CASH_FLOW&symbol={self.company_ticker}&apikey={self.alpha_vantage_key}"
        ).json()

        return CashFlowStat["annualReports"]

    def check_annual_vs_quarterly(self, report):
        "This method will check if the data is annual or quarterly"
        # check if the annual or quarlty data is newer
        annual_statement_date = report["annualReports"][0]["fiscalDateEnding"]
        quarterly_statement_date = report["quarterlyReports"][0]["fiscalDateEnding"]

        if annual_statement_date > quarterly_statement_date:
            report_to_use = "annualReports"
        else:
            report_to_use = "quarterlyReports"

        return report[report_to_use][0]

    def calculate_nwc(self, df):
        "This method will calculate the net working capital for a company"
        # calculate the net working capital
        # TODO: add the ability to calculate the net working capital if cash should be included
        df["netWorkingCapital"] = (
            df["totalCurrentAssets"] - df["cashAndShortTermInvestments"]
        ) - (df["totalCurrentLiabilities"] - df["currentDebt"])
        pass


if __name__ == "__main__":

    model = DCFModel("AAPL")
    model.calculate_dcf()

    print(model.ev)
    print(model.equity_value)
