# Importing required modules
import pandas as pd
import numpy as np
import numpy as np
import plotly.express as px

# To extract and parse fundamental data like beta and growth estimates from finviz website
import requests
from bs4 import BeautifulSoup as bs

# For parsing financial statements data from financialmodelingprep api
from urllib.request import urlopen
import json

# For Gradio App
import gradio as gr

import os
# uncomment and set API Key in the environment variable below
# or you can choose to set it using any other method you know
#os.environ['FMP_API_KEY'] = "your_api_key"

# read the environment variable to use in API requests later
apiKey = os.environ['FMP_API_KEY']


############################################################################################################
###### GET DATA FROM FINANCIAL MODELING PREP
############################################################################################################

# Financialmodelingprep api url
base_url = "https://financialmodelingprep.com/api/v3/"

def get_jsonparsed_data(url):
    response = urlopen(url)
    data = response.read().decode("utf-8")
    return json.loads(data)

# get financial statements using financial modelling prep API
def get_financial_statements(ticker):
    # quarterly cash flow statements for calculating latest trailing twelve months (TTM) free cash flow
    columns_drop = ['acceptedDate', 'period', 'symbol', 'reportedCurrency', 'cik', 'fillingDate', 'depreciationAndAmortization', 'link', 'finalLink']
    q_cash_flow_statement = pd.DataFrame(get_jsonparsed_data(base_url+'cash-flow-statement/' + ticker + '?period=quarter' + '&apikey=' + apiKey))
    q_cash_flow_statement = q_cash_flow_statement.set_index('date').drop(columns_drop, axis=1).iloc[:4] # extract for last 4 quarters
    latest_year = int(q_cash_flow_statement.iloc[0]['calendarYear'])

    # annual cash flow statements
    cash_flow_statement = pd.DataFrame(get_jsonparsed_data(base_url+'cash-flow-statement/' + ticker + '?apikey=' + apiKey))
    cash_flow_statement = cash_flow_statement.set_index('date').drop(columns_drop, axis=1)
    
    # combine annual and latest TTM cash flow statements
    ttm_cash_flow_statement = q_cash_flow_statement.sum() # sum up last 4 quarters to get TTM cash flow
    cash_flow_statement = cash_flow_statement[::-1].append(ttm_cash_flow_statement.rename('TTM')).drop(['netIncome'], axis=1)
    final_cash_flow_statement = cash_flow_statement[::-1] # reverse list to show most recent ones first
    
    # quarterly balance sheet statements
    columns_drop = ['acceptedDate', 'calendarYear', 'period', 'symbol', 'reportedCurrency', 'cik', 'fillingDate', 'link', 'finalLink']
    q_balance_statement = pd.DataFrame(get_jsonparsed_data(base_url+'balance-sheet-statement/' + ticker + '?' + '&apikey=' + apiKey))
    q_balance_statement = q_balance_statement.set_index('date').drop(columns_drop, axis=1)
    q_balance_statement = q_balance_statement.apply(pd.to_numeric, errors='coerce')
        
    return q_cash_flow_statement, cash_flow_statement, final_cash_flow_statement, q_balance_statement, latest_year
 
 
# check stability of cash flows
def plot_cash_flow(ticker, cash_flow_statement):       
    # DCF model works best only if the free cash flows are POSITIVE, STABLE and STEADILY INCREASING. 
    # So let's plot the graph and verify if this is the case.
    fig_cash_flow = px.bar(cash_flow_statement , y='freeCashFlow', title=ticker + ' Free Cash Flows')
    fig_cash_flow.update_xaxes(type='category', tickangle=270, title='Date')
    fig_cash_flow.update_yaxes(title='Free Cash Flows')
    #fig_cash_flow.show()
    return fig_cash_flow


# get ttm cash flow, most recent total debt and cash & short term investment data from statements
def get_statements_data(final_cash_flow_statement, q_balance_statement):
    cash_flow = final_cash_flow_statement.iloc[0]['freeCashFlow'] # ttm cash flow
    total_debt = q_balance_statement.iloc[0]['totalDebt'] 
    cash_and_ST_investments = q_balance_statement.iloc[0]['cashAndShortTermInvestments']
    return cash_flow, total_debt, cash_and_ST_investments


############################################################################################################
###### GET DATA FROM FINVIZ WEBSITE 
############################################################################################################

# Price, EPS next Y/5Y, Beta, Number of Shares Outstanding
# Extract (using requests.get) and Parse (using Beautiful Soup) data from Finviz table in the Finviz website (see screenshot above), needed to calculate intrinsic value of stock.

# List of data we want to extract from Finviz Table
# Price is the current stock price
# EPS next Y is the estimated earnings growth for next year
# EPS next 5Y is the estimated earnings growth for next 5 years (if this is not present on finviz, we will use EPS next Y instead)
# Beta captures the volatility of the stock, used for estimating discount rate later
# Shs Outstand is the number of shares present in the market
metric = ['Price', 'EPS next Y', 'EPS next 5Y', 'Beta', 'Shs Outstand']

def fundamental_metric(soup, metric):
    # the table which stores the data in Finviz has html table attribute class of 'snapshot-td2'
    return soup.find_all(text = metric)[-1].find_next(class_='snapshot-td2').text

# get above metrics from finviz and store as a dict 
def get_finviz_data(ticker):
    try:
        url = ("http://finviz.com/quote.ashx?t=" + ticker.lower())
        soup = bs(requests.get(url,headers={'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64; rv:20.0) Gecko/20100101 Firefox/20.0'}).content)
        dict_finviz = {}        
        for m in metric:   
            dict_finviz[m] = fundamental_metric(soup,m)
        for key, value in dict_finviz.items():
            # replace percentages
            if (value[-1]=='%'):
                dict_finviz[key] = value[:-1]
                dict_finviz[key] = float(dict_finviz[key])
            # billion
            if (value[-1]=='B'):
                dict_finviz[key] = value[:-1]
                dict_finviz[key] = float(dict_finviz[key])*1000000000  
            # million
            if (value[-1]=='M'):
                dict_finviz[key] = value[:-1]
                dict_finviz[key] = float(dict_finviz[key])*1000000
            try:
                dict_finviz[key] = float(dict_finviz[key])
            except:
                pass 
    except Exception as e:
        print (e)
        print ('Not successful parsing ' + ticker + ' data.')        
    return dict_finviz


def parse_finviz_dict(finviz_dict):
    EPS_growth_5Y = finviz_dict['EPS next 5Y']
    # sometimes EPS next 5Y is empty and shows as a '-' string, in this case use EPS next Y
    if isinstance(EPS_growth_5Y, str):
        if not EPS_growth_5Y.isdigit():
            EPS_growth_5Y = finviz_dict['EPS next Y']
    EPS_growth_6Y_to_10Y = EPS_growth_5Y/2  # Half the previous growth rate, conservative estimate
    # Long term = previous growth rate or around long term inflation rate, whichever is lower to be conservative estimate
    long_term_growth_rate = np.minimum(EPS_growth_6Y_to_10Y, 3)
    shares_outstanding = finviz_dict['Shs Outstand']
    beta = finviz_dict['Beta']
    current_price = finviz_dict['Price']
    
    return EPS_growth_5Y, EPS_growth_6Y_to_10Y, long_term_growth_rate, beta, shares_outstanding, current_price


## Estimate Discount Rate from Beta
def estimate_discount_rate(beta):
    # Beta shows the volatility of the stock, 
    # the higher the beta, we want to be more conservative by increasing the discount rate also.
    discount_rate = 7
    if(beta<0.80):
        discount_rate = 5
    elif(beta>=0.80 and beta<1):
        discount_rate = 6
    elif(beta>=1 and beta<1.1):
        discount_rate = 6.5
    elif(beta>=1.1 and beta<1.2):
        discount_rate = 7
    elif(beta>=1.2 and beta<1.3):
        discount_rate = 7.5
    elif(beta>=1.3 and beta<1.4):
        discount_rate = 8
    elif(beta>=1.4 and beta<1.6):
        discount_rate = 8.5
    elif(beta>=1.61):
        discount_rate = 9   

    return discount_rate


############################################################################################################
## Calculate Intrinsic Value
############################################################################################################

# 1. First Project Cash Flows from Year 1 to Year 10 using Present (TTM) Free Cash Flow
# 2. Discount the Cash Flows to Present Value
# 3. Calculate the Terminal Value after Year 10 (Discounted to Present Value) Assuming the Company will Grow at a Constant Steady Rate Forever (https://corporatefinanceinstitute.com/resources/financial-modeling/dcf-terminal-value-formula/)
# 4. Add the Cash Flows and the Terminal Value Up
# 5. Then Account for the Cash + Short Term Investments and Subtract Total Debt
# 6. Divide by Total Number of Shares Outstanding

def calculate_intrinsic_value(latest_year, cash_flow, total_debt, cash_and_ST_investments, 
                                  EPS_growth_5Y, EPS_growth_6Y_to_10Y, long_term_growth_rate,
                                  shares_outstanding, discount_rate, current_price):   
    
    # Convert all percentages to decmials
    EPS_growth_5Y_d = EPS_growth_5Y/100
    EPS_growth_6Y_to_10Y_d = EPS_growth_6Y_to_10Y/100
    long_term_growth_rate_d = long_term_growth_rate/100
    discount_rate_d = discount_rate/100
    # print("Discounted Cash Flows\n")
    
    # Lists of projected cash flows from year 1 to year 20
    cash_flow_list = []
    cash_flow_discounted_list = []
    year_list = []
        
    # Years 1 to 5
    for year in range(1, 6):
        year_list.append(year + latest_year)
        cash_flow*=(1 + EPS_growth_5Y_d)        
        cash_flow_list.append(cash_flow)
        cash_flow_discounted = cash_flow/((1 + discount_rate_d)**year)
        cash_flow_discounted_list.append(cash_flow_discounted)
        # print("Year " + str(year + latest_year) + ": $" + str(cash_flow_discounted)) ## Print out the projected discounted cash flows
    
    # Years 6 to 10
    for year in range(6, 11):
        year_list.append(year + latest_year)
        cash_flow*=(1 + EPS_growth_6Y_to_10Y_d)
        cash_flow_list.append(cash_flow)
        cash_flow_discounted = cash_flow/((1 + discount_rate_d)**year)
        cash_flow_discounted_list.append(cash_flow_discounted)
        # print("Year " + str(year + latest_year) + ": $" + str(cash_flow_discounted)) ## Print out the projected discounted cash flows
    
    # Store all forecasted cash flows in dataframe
    forecast_cash_flows_df = pd.DataFrame.from_dict({'Year': year_list, 'Cash Flow': cash_flow_list, 'Discounted Cash Flow': cash_flow_discounted_list})
    forecast_cash_flows_df = forecast_cash_flows_df.set_index('Year')
    
    # Growth in Perpuity Approach    
    cashflow_10Y = cash_flow_discounted_list[-1]
    # Formula to Calculate: https://corporatefinanceinstitute.com/resources/financial-modeling/dcf-terminal-value-formula/
    terminal_value = cashflow_10Y*(1+long_term_growth_rate_d)/(discount_rate_d-long_term_growth_rate_d)
    
    # Yay finally
    intrinsic_value = (sum(cash_flow_discounted_list) + terminal_value - total_debt + cash_and_ST_investments)/shares_outstanding
    margin_of_safety = (1-current_price/intrinsic_value)*100
    
    return forecast_cash_flows_df, terminal_value, intrinsic_value, margin_of_safety


# Plot forecasted cash flows from years 1 to 10, as well as the discounted cash flows
def plot_forecasted_cash_flows(ticker, forecast_cash_flows_df):

    fig_cash_forecast = px.bar(forecast_cash_flows_df, barmode='group', title=ticker + ' Projected Free Cash Flows')
    fig_cash_forecast.update_xaxes(type='category', tickangle=270)
    fig_cash_forecast.update_xaxes(tickangle=270, title='Forecasted Year')
    fig_cash_forecast.update_yaxes(title='Free Cash Flows')
    # fig_cash_forecast.show()
    
    return fig_cash_forecast


# chain all the steps from the functions above together
def run_all_steps(ticker):
    ticker = ticker.upper() # make sure ticker is caps
    
    q_cash_flow_statement, cash_flow_statement, final_cash_flow_statement, q_balance_statement, latest_year = get_financial_statements(ticker)

    fig_cash_flow = plot_cash_flow(ticker, cash_flow_statement)

    cash_flow, total_debt, cash_and_ST_investments = get_statements_data(final_cash_flow_statement, q_balance_statement)

    finviz_dict = get_finviz_data(ticker)

    EPS_growth_5Y, EPS_growth_6Y_to_10Y, long_term_growth_rate, beta, shares_outstanding, current_price = parse_finviz_dict(finviz_dict)

    discount_rate = estimate_discount_rate(beta)

    forecast_cash_flows_df, terminal_value, intrinsic_value, margin_of_safety = calculate_intrinsic_value(latest_year, cash_flow, total_debt, cash_and_ST_investments, 
                                      EPS_growth_5Y, EPS_growth_6Y_to_10Y, long_term_growth_rate,
                                      shares_outstanding, discount_rate, current_price)

    fig_cash_forecast = plot_forecasted_cash_flows(ticker, forecast_cash_flows_df)
    
    return q_cash_flow_statement.reset_index(), final_cash_flow_statement.reset_index(), q_balance_statement.reset_index(), fig_cash_flow, \
        str(EPS_growth_5Y) + '%', str(EPS_growth_6Y_to_10Y) + '%', str(long_term_growth_rate) + '%', \
        beta, shares_outstanding, current_price, \
        str(discount_rate) + '%', forecast_cash_flows_df.reset_index(), terminal_value, intrinsic_value, fig_cash_forecast, margin_of_safety
      

# Gradio App and UI
with gr.Blocks() as app:
    with gr.Row():
        gr.HTML("<h1>Bohmian's Stock Intrinsic Value Calculator</h1>")

    with gr.Row():        
        ticker = gr.Textbox("AAPL", label='Enter stock ticker to calculate its intrinsic value e.g. "AAPL"')
        btn = gr.Button("Calculate Intrinsic Value")
        
    # Show intrinsic value calculation results
    with gr.Row():
        gr.HTML("<h2>Calculated Intrinsic Value</h2>")
        
    with gr.Row():
        intrinsic_value = gr.Text(label="Intrinsic Value (if this value is negative, it means current cash flow may be negative and this model WOULD NOT WORK, scroll down to check)")
        current_price = gr.Text(label="Actual Stock Price")
        margin_of_safety = gr.Text(label="Margin of Safety")
    
    # Show metrics obtained and estimated from FinViz website that were essential for calculations
    with gr.Row():
        gr.HTML("<h2>Metrics Obtained (and Estimated) from FinViz Website</h2>")
    with gr.Row():
        gr.HTML("<h3>https://finviz.com/</h3>")
        
    with gr.Row():
            EPS_growth_5Y = gr.Text(label="EPS Next 5Y (estimated EPS growth for next 5 years)")
            EPS_growth_6Y_to_10Y = gr.Text(label="EPS growth for 6th to 10th year (estimated as half of above)")
            long_term_growth_rate = gr.Text(label="Long Term Growth Rate (estimated as the above or 3%, whichever is lower)")  
    
    with gr.Row():
        beta = gr.Text(label="Beta (measures volatility of stock)")
        discount_rate = gr.Text(label="Discount Rate (estimated from beta)") 
        shares_outstanding = gr.Text(label="Shares Outstanding")             

       
    # Show detailed actual historical financial statements
    with gr.Row():
        gr.HTML("<h2>Actual Historical Financial Statements Data from Financial Modelling Prep API</h2>")
    with gr.Row():
        gr.HTML("<h3>https://site.financialmodelingprep.com/developer</h3>")
    with gr.Row():
        gr.HTML("<h3>IMPORTANT NOTE: DCF model works best only if the free cash flows are POSITIVE, STABLE and STEADILY INCREASING. Check if this is the case.</h3>")
    
    with gr.Row():        
        fig_cash_flow = gr.Plot(label="Historical Cash Flows")
        
    with gr.Row():   
        q_cash_flow_statement = gr.DataFrame(label="Last 4 Quarterly Cash Flow Statements")
        
    with gr.Row():   
        final_cash_flow_statement = gr.DataFrame(label="TTM + Annual Cash Flow Statements")
    
    with gr.Row():   
        q_balance_statement = gr.DataFrame(label="Quarterly Balance Statements")
    
    
    # Show forecasted cash flows and terminal value
    with gr.Row():
        gr.HTML("<h2>Forecasted Cash Flows for Next 10 Years</h2>")
        
    with gr.Row():
        fig_cash_forecast = gr.Plot(label="Forecasted Cash Flows")
    
    with gr.Row():    
        forecast_cash_flows_df = gr.DataFrame(label="Forecasted Cash Flows")

    with gr.Row():
        terminal_value = gr.Text(label="Terminal Value (after 10th year)")
        

    btn.click(fn=run_all_steps, inputs=[ticker], 
              outputs=[q_cash_flow_statement, final_cash_flow_statement, q_balance_statement, fig_cash_flow, \
        EPS_growth_5Y, EPS_growth_6Y_to_10Y, long_term_growth_rate, beta, shares_outstanding, current_price, \
        discount_rate, forecast_cash_flows_df, terminal_value, intrinsic_value, fig_cash_forecast, margin_of_safety])

app.launch()