import os
import pandas as pd
import yfinance as yf
from yahoo_fin import stock_info as si
#from alpha_vantage.timeseries import TimeSeries
import requests

from datetime import datetime, timedelta
# from ta.volatility import AverageTrueRange # Commented out because it's mentioned to conflict with yfinance and not used anyway
import numpy as np
import math
import random
import time

SIMU=0

def __check_ticker_is_equity_on_us_market(ticker):
    try:
        ticker_obj = yf.Ticker(ticker)
        if ticker_obj is not None:
            ticker_history = ticker_obj.history(period="1mo")
            if not ticker_history.empty:
                ticker_info = ticker_obj.info
                if ticker_info is not None and ticker_info['quoteType'] == 'EQUITY' and ticker_info['region'] =='US':
                    return True, None
                else:
                    return False, f"{ticker} not EQUITY - OR - not in US"
            else:
                return False, f"{ticker} yf.Ticker returned None"
        else:
            return False, f"{ticker} yf.Ticker returned None"
    except Exception as e:
        return False, f"{ticker}: An error occurred while retrieving ticker information: {e}"

""" def __get_sector(ticker):
    try:
        response = requests.get(f"https://www.alphavantage.co/query?function=OVERVIEW&symbol={ticker}&apikey={api_key}")
        sec = response.json()["Sector"]            
    except (Exception, ValueError, KeyError) as e:
        sec = "Unknown"
    return sec, None """


def __get_cap(ticker):
    try:
        ticker = yf.Ticker(ticker)
        if ticker.info is not None:
            cap = ticker.info.get('marketCap', 0)
        else:
            return -1, f"{ticker}: ticker not found in yfinance"
        if cap==0:
            return -1, f"{ticker}: yfinance market cap is 0"
    except (Exception, ValueError, KeyError) as e:
        return -1, str(e)
    return cap, None

def __get_cap_yahoo_fin(ticker):
    try:
        quote_table = si.get_quote_table(ticker)
        cap = quote_table["Market Cap"]
        if cap==0:
            return -1, f"{ticker}: yahoo_fin market cap is 0"
        if cap[-1] == 'M':
            cap = float(cap[:-1]) * 1000000
        elif cap[-1] == 'B':
            cap = float(cap[:-1]) * 1000000000
        elif cap[-1] == 'T':
            cap = float(cap[:-1]) * 1000000000000
        else:
            cap = float(cap)
    except (Exception, ValueError, KeyError) as e:
        return -1, str(e)
    return cap, None


def __get_cap_alphavantage(ticker):
    api_key = "_9V69NQK8CA05QBZQ" 
    try:
        response = requests.get(f"https://www.alphavantage.co/query?function=OVERVIEW&symbol={ticker}&apikey={api_key}")
        response.raise_for_status() # Lève une exception pour les erreurs HTTP (4xx ou 5xx)
        data = response.json()
        return int(data["MarketCapitalization"]), None
        
    except requests.exceptions.HTTPError as e:
        return None, f"Erreur HTTP lors de l'appel à l'API alphavantage: {e}"
    except (ValueError, KeyError) as e:
        return None, f"Erreur lors de l'analyse de la réponse JSON de l'API alphavantage: {e}"


def __get_quotes(ticker, days=630, interval_='1d'):
    end_date = datetime.now()
    start_date = end_date - timedelta(days)

    data = pd.DataFrame()
    time_delta = timedelta(days=414)

    while start_date < end_date:
        try:
            temp_data = yf.download(ticker, start=start_date, end=min(start_date + time_delta, end_date), interval=interval_)
            if temp_data.empty:
                print(f"No data found for {ticker} between {start_date} and {start_date + time_delta}")
            else:
                data = pd.concat([data, temp_data])
        except KeyError:
            print(f"Error: Data for ticker {ticker} not found.")
            return None, f"Error: Data for ticker {ticker} not found."
        except Exception as e:
            print(f"An error occurred while fetching data for {ticker}: {e}")
            return None, f"An error occurred while fetching data for {ticker}: {e}"

        start_date += time_delta

    if data.empty:
        return None, f"No data found for {ticker}"

    return data, None

def __calculate_historical_volatility(quotes, maxdays=630):
    try:
        maxdays = min(maxdays, len(quotes))
        quotes['Returns'] = np.log(quotes['Close'] / quotes['Close'].shift(1))

        historical_volatility = quotes['Returns'].std()
        annualized_volatility = historical_volatility * np.sqrt(252)

        return annualized_volatility, None
    except (Exception, ValueError, KeyError) as e:
        return -1, str(e)


def __calculate_sma(quotes, nr_of_period=200): 
    if len(quotes) < nr_of_period:
        return -1, f"Not enough data for SMA calculation"
    else:
        quotes['SMA'] = quotes['Close'].rolling(window=nr_of_period).mean()
        return quotes['SMA'].iloc[-1], None

def __is_quote_above_sma(price, sma_value):
    if sma_value is None or price is None:
        return -1, "price or sma_value is missing"
    return bool(price > sma_value), None

def __get_last_price(quotes):
    if SIMU == 0:
        return float(quotes["Close"].iloc[-1]), None
    else:
        return random.uniform(5, 10), None

def __get_ROC(quotes, nr_of_period=1):
    if SIMU == 0:
        if len(quotes) >= nr_of_period + 1:
            roc = ((quotes["Close"].iloc[-1] - quotes["Close"].iloc[-1 - nr_of_period]) / quotes["Close"].iloc[-1 - nr_of_period])
            return float(roc), None
        else:
            return -1, "ROC measure: not enough data"
    else:
        return random.uniform(-0.3, 0.3), None


# get all required data for each ticker
def get_data(tickers_csv, SEPARATOR, data_csv, acceptable_nr_of_invalid_tickers):
    
    valid_tickers_data = {}

    try:
        os.remove(data_csv)
    except FileNotFoundError:
        pass        
    finally:
        with open(data_csv, "w") as f:
           f.close()
     
    df = pd.read_csv(tickers_csv, sep=SEPARATOR)
    nr_of_invalid_tickers=0

    # Utiliser la taille du DataFrame pour le nombre total de tickers
    total_tickers = len(df['Ticker'])

    for index, ticker in enumerate(df['Ticker']):
        # Afficher la progression réelle
        print(f"Traitement: {index+1} sur {total_tickers} ({((index+1)/total_tickers)*100:.1f}%)")
        
        ticker_is_valid = True
        time.sleep(1.2)
        ## TODO: catch rate limit exception and retry same ticker
        #is_valid_ticker, error = __check_ticker_is_equity_on_us_market(ticker)
        is_valid_ticker=True
        if is_valid_ticker:

            print(ticker)
            d_quotes, error = __get_quotes(ticker, math.ceil(31 * 365 / 12), '1d')
            if error or len(d_quotes)<210: 
                ticker_is_valid = False
                if error:
                    print(f"{ticker}: __get_quotes: daily: {error}")
                else:
                    print(f"{ticker}: __get_quotes: daily: length of  quotes is lower than 210 days")
            else:                
                try:
                    ticker_index = df.loc[df['Ticker'] == ticker].index[0]
                    sec = None#df.at[ticker_index, 'Sector']
                    if pd.isna(sec): 
                        sec = "-"
                except IndexError:
                    sec = "-"
                
                cap, error = __get_cap(ticker)
                if error:
                    print(f"{ticker}: No Market cap available from yfinance, now try with yahoo_fin...")
                    cap, error = __get_cap_yahoo_fin(ticker)
                    if error: 
                        print (f"{ticker}: Market Cap invalid from yfinance and yahoo_fin,  now try with alphavantage... ")
                        cap, error = __get_cap_alphavantage(ticker)
                        if error: 
                            ticker_is_valid = False
                            print (f"{ticker}: Market Cap invalid from yfinance + yahoo_fin + alphavantage")
                        else:
                            print(f"{ticker}: Market Cap from alphavantage OK")
                    else:
                        print(f"{ticker}: Market Cap from yahoo_fin OK")
                else:
                    print(f"{ticker}: Market Cap from yfinance OK")
                
                vol, error = __calculate_historical_volatility(d_quotes, 630)
                if error: 
                    ticker_is_valid = False
                    print(f"{ticker}: __calculate_historical_volatility: {error}")

                
                w_quotes, error = __get_quotes(ticker, math.ceil(11 * 365 / 12), '1wk')
                if error or len(w_quotes)<40: 
                    ticker_is_valid = False
                    if error:
                        print(f"{ticker}: __get_quotes: weekly: {error}")
                    else:
                        print(f"{ticker}: __get_quotes: weekly: length of quotes is lower than 40 weeks")
                else:
                    w_sma, error = __calculate_sma(w_quotes, 40)
                    if error: 
                        ticker_is_valid = False
                        print(f"{ticker}: __calculate_sma: weekly: {error}")

                    w_price, error = __get_last_price(w_quotes)
                    if error: 
                        ticker_is_valid = False
                        print(f"{ticker}: __get_last_price: weekly: {error}")

                    w_price_above_sma, error = __is_quote_above_sma(w_price, w_sma)
                    if error: 
                        ticker_is_valid = False
                        print(f"{ticker}: __is_quote_above_sma: weekly: {error}")

                    w_roc, error = __get_ROC(w_quotes, 13)
                    if error: 
                        ticker_is_valid = False
                        print(f"{ticker}: __get_ROC: weekly: {error}")
                         
                
                m_quotes, error = __get_quotes(ticker, math.ceil(11 * 365 / 12), '1mo')
                if error or len(m_quotes)<10: 
                    ticker_is_valid = False
                    if error:
                        print(f"{ticker}: __get_quotes: monthly: {error}")
                    else:
                        print(f"{ticker}: __get_quotes: monthly: length of quotes is lower than 10 months")
                else:
                    m_sma, error = __calculate_sma(m_quotes, 10)
                    if error: 
                        ticker_is_valid = False
                        print(f"{ticker}: __calculate_sma: monthly: {error}")

                    m_price, error = __get_last_price(m_quotes)
                    if error: 
                        ticker_is_valid = False
                        print(f"{ticker}: __get_last_price: monthly: {error}")

                    m_price_above_sma, error = __is_quote_above_sma(m_price, m_sma)
                    if error: 
                        ticker_is_valid = False
                        print(f"{ticker}: __is_quote_above_sma: monthly: {error}")

                    m_roc, error = __get_ROC(m_quotes, 3)
                    if error: 
                        ticker_is_valid = False
                        print(f"{ticker}: __get_ROC: monthly: {error}")
                    
        else:
            print(f"{ticker}: {error}")
            ticker_is_valid = False
        
        if ticker_is_valid: 
            # Stockez les données de chaque ticker valide dans un dictionnaire
            valid_tickers_data[ticker] = {
                "Sector": sec,
                "Market Cap": cap,
                "Volat. 30M annual.": vol,
                "PriceW": w_price,
                "SMA40W": w_sma,
                "over SMA40W": w_price_above_sma,
                "ROC13W": w_roc,
                "PriceM": m_price,
                "SMA10M": m_sma,
                "over SMA10M": m_price_above_sma,
                "ROC3M": m_roc
            }
            # Créer et écrire le DataFrame pour ce ticker
            temp_df = pd.DataFrame.from_dict({ticker: valid_tickers_data[ticker]}, orient="index")
            temp_df.index.name = "Ticker"
            temp_df.reset_index(inplace=True)
            # Écrire avec ou sans en-tête selon si le fichier est vide
            file_is_empty = os.path.getsize(data_csv) == 0
            temp_df.to_csv(data_csv, mode='a', header=file_is_empty, index=False, sep=SEPARATOR)
        else:
            nr_of_invalid_tickers = nr_of_invalid_tickers + 1
            print(f"{ticker}: ticker ignored")
            # Afficher le nombre de tickers invalides après chaque échec
            print(f"Nombre de tickers invalides: {nr_of_invalid_tickers}/{acceptable_nr_of_invalid_tickers}")
            
            # Vérifier si le nombre de tickers invalides dépasse la limite acceptable
            if nr_of_invalid_tickers > acceptable_nr_of_invalid_tickers:
                print(f"Nr of invalid tickers ({nr_of_invalid_tickers}) higher than the acceptable limit ({acceptable_nr_of_invalid_tickers})")
                return "Too many invalid tickers"

    #print(valid_tickers_data)

    # Convertissez le dictionnaire des données valides en un DataFrame
    output_df = pd.DataFrame.from_dict(valid_tickers_data, orient="index")
    output_df.index.name = "Ticker"
    output_df.reset_index(inplace=True)
    output_df.to_csv(data_csv, index=False, sep=SEPARATOR)

    return None


