import pandas as pd
from flask import Flask, render_template, request, redirect, url_for
import os
import time #used for timestamps to ensure no cache issues
from datetime import timedelta #for smoothing over number of periods
import numpy as np
from statsmodels.tsa.holtwinters import ExponentialSmoothing as HoltWinters #Holt Winters smoothing for trend and seasonality
import plotly.graph_objects as go
import plotly
import json

app = Flask(__name__)
    
def forecast_next_year(dates, points, steps=52): #used for the smoothing forecasts
    points_array = np.array(points.values, dtype=float)
    
    try:
        if len(points_array) >= 26: #need at least this many data points for seasonality
            model = HoltWinters(points_array, trend='add', damped_trend=True, seasonal='add', seasonal_periods=13)
        elif len(points_array) >= 10: #still employ trend if 10-25 datapoints
            model = HoltWinters(points_array, trend='add', damped_trend=True, seasonal=None)
        else:
            raise ValueError("Too little data")

        fit = model.fit(optimized=True)
        forecast_values = fit.forecast(steps).tolist() #converts model results to list
        
        last_actual = points_array[-1]
        if max(forecast_values) > last_actual * 5 or min(forecast_values) < 0: #setting these as unreasonable predictions
            raise ValueError("Forecast out of reasonable range")
            
    except Exception as e:
        forecast_values = [float(points_array[-1])] * steps

    last_date = dates.iloc[-1]
    forecast_dates = [last_date + timedelta(weeks=i+1) for i in range(steps)]
    return forecast_dates, forecast_values
    
def calculate_cusum(player_df, full_df, T=10000, mu=400, rank_col='Ranking'):
    player_df = player_df.sort_values('Date').copy()
    cusum_values = [0]
    for i in range(1, len(player_df)):
        prev_cusum = cusum_values[i-1]
        current_points = player_df['Points'].iloc[i]
        new_cusum = max(0, current_points + prev_cusum - mu)
        cusum_values.append(new_cusum)
    player_df['CUSUM'] = [int(v) for v in cusum_values]

    player_name = player_df['Name'].iloc[0]
    player_full_data = full_df[full_df['Name'] == player_name]

    player_full_data = player_full_data.copy()
    player_full_data[rank_col] = pd.to_numeric(player_full_data[rank_col], errors='coerce')
    has_broken_out = (player_full_data[rank_col] <= 50).any() #do not predict if they were formerly in the top 50

    if has_broken_out:
        player_df['Breakout'] = '*'
    else:
        player_df['Breakout'] = player_df['CUSUM'].apply(
            lambda x: 'Breakout Predicted' if x >= T else ''
        )

    return player_df

@app.route('/')
def home():
    names = generate_list('ATP.csv')
    return render_template('homepage.html')

def generate_list(csv_file_path):
    # Read data from CSV file using pandas
    df = pd.read_csv(csv_file_path)
    
    latest = df['Date'].max() 

    current_df = df[df['Date'] == latest]

    allNames = current_df['Name'].sort_values() #names present in most recent rankings data
    return allNames.tolist()

@app.route('/graphs')
def graphs():
    csv_file_path = 'ATP.csv'
    n = generate_list(csv_file_path)
    return render_template('graphs.html', names=n)
    
@app.route('/breakout')
def breakout():
    df = pd.read_csv('ATP.csv')
    df['Date'] = pd.to_datetime(df['Date'], format='%m/%d/%Y')
    df['Points'] = df['Points'].str.replace(',', '').astype(float)

    latest = df['Date'].max()
    current_df = df[df['Date'] == latest].copy()
    current_df = current_df[(current_df['Ranking'] >= 101) & (current_df['Ranking'] <= 500)] #Only looking at ranking 101-500
    current_df = current_df.sort_values('Ranking')

    results = []
    for _, row in current_df.iterrows():
        player_history = df[df['Name'] == row['Name']]
        player_history = calculate_cusum(player_history, full_df=df)  # <-- pass df here
        latest_row = player_history[player_history['Date'] == latest].iloc[0]
        results.append({
            'Ranking': int(row['Ranking']),
            'Name': row['Name'],
            'Age': int(row['Age']),
            'Points': int(row['Points']),
            'CUSUM': int(latest_row['CUSUM']),  # <-- no decimals
            'Breakout': latest_row['Breakout']
        })
    return render_template('breakout.html', players=results)



@app.route('/dropDownFormHandler', methods=['GET', 'POST'])
def dropDownFormHandler():
    if request.method == 'POST':
        player1 = request.form.get('nameList')
        player2 = request.form.get('nameList2')
        if not player1: #player1 must be selected
            return redirect(url_for('graphs'))

        if not player2 or player2.strip() == '':
            player2 = None #If player2 is not selected that is fine

        if player2:
            title = f'Ranking Points: {player1} vs {player2}' #combining both player names into the string
        else:
            title = f'Ranking Points for {player1}'

        df = pd.read_csv('ATP.csv')
        df['Date'] = pd.to_datetime(df['Date'], format='%m/%d/%Y')
        df['Points'] = df['Points'].str.replace(',', '').astype(float) #changing data types

        filtered1 = df[df['Name'] == player1].copy().sort_values('Date').reset_index(drop=True)
        
        forecast_dates1, forecast_values1 = forecast_next_year(filtered1['Date'], filtered1['Points'])

        def make_hover(filtered): #for plotly functionality
            return [
                f"Name: {row['Name']}<br>Date: {row['Date'].strftime('%Y-%m-%d')}<br>Age: {row['Age']}<br>Points: {row['Points']}<br>Ranking: {row['Ranking']}"
                for _, row in filtered.iterrows()
            ]

        fig = go.Figure() #start of plotly plot

        fig.add_trace(go.Scatter(
            x=filtered1['Date'].tolist(),
            y=filtered1['Points'].tolist(),
            mode='lines+markers',
            name=player1,
            hovertext=make_hover(filtered1),
            hoverinfo='text' #for actual data
        ))

        fig.add_trace(go.Scatter(
            x=forecast_dates1,
            y=forecast_values1,
            mode='lines',
            name=player1 + ' forecast',
            line=dict(dash='dash'),
            hoverinfo='skip' #for predictions, at the moment not letting it hover
        ))

        if player2: #a second player has been selected
            filtered2 = df[df['Name'] == player2].copy().sort_values('Date').reset_index(drop=True)
            forecast_dates2, forecast_values2 = forecast_next_year(filtered2['Date'], filtered2['Points'])

            fig.add_trace(go.Scatter(
                x=filtered2['Date'].tolist(),
                y=filtered2['Points'].tolist(),
                mode='lines+markers',
                name=player2,
                hovertext=make_hover(filtered2),
                hoverinfo='text'
            ))

            fig.add_trace(go.Scatter(
                x=forecast_dates2,
                y=forecast_values2,
                mode='lines',
                name=player2 + ' forecast',
                line=dict(dash='dash'),
                hoverinfo='skip' #Again, no hovering for predictions
            ))

            y_min = min(filtered1['Points'].min(), filtered2['Points'].min(),
                        min(forecast_values1), min(forecast_values2)) #more min and max possibilities needed with 2 players
            y_max = max(filtered1['Points'].max(), filtered2['Points'].max(),
                        max(forecast_values1), max(forecast_values2))
        else:
            y_min = min(filtered1['Points'].min(), min(forecast_values1))
            y_max = max(filtered1['Points'].max(), max(forecast_values1))

        fig.update_layout(
            title=title,
            xaxis_title='Date',
            yaxis_title='Points',
            hovermode='closest',
            template='plotly_white',
            yaxis=dict(range=[max(0, y_min * 0.9), y_max * 1.1]) #adds padding
        )

        plot_json = json.dumps(fig, cls=plotly.utils.PlotlyJSONEncoder) #plot data in json file
        timestamp = int(time.time())
        return render_template('showPlot.html', theName=title, plot_json=plot_json, theTable=None, ts=timestamp)  
        
if __name__ == '__main__':
    # Ensure to have a data.csv file in the root directory for this example to work
    app.run(debug=True)
