# All edits from the original from the initial fork have been done by:
# Michael Zarate

from flask import Flask, render_template, request, redirect, jsonify, url_for, flash
from sqlalchemy import create_engine, asc
from sqlalchemy.orm import sessionmaker
from database_setup import Base, Restaurant, MenuItem, User
import random
import string
import app_config as cfg

# New Import block for client sessions and Auth
from flask import session as login_session
# NOTE: oauth2client is deprecated and they recommend using google-auth
from oauth2client.client import flow_from_clientsecrets
from oauth2client.client import FlowExchangeError
import httplib2
import json
from flask import make_response
import requests

# END IMPORTS #

# This will allow us to read in the JSON object provided by google
CLIENT_ID = json.loads(open('client_secrets.json', 'r').read())['web']['client_id']


app = Flask(__name__)

# Connect to Database and create database session
engine = create_engine('sqlite:///restaurantmenuwithusers.db')
Base.metadata.bind = engine

DBSession = sessionmaker(bind=engine)
session = DBSession()


# Create a state token to prevent request forgery.
# We will store in the session for later validation
@app.route('/login')
def showLogin():
    state = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(32))
    login_session['state'] = state
    return render_template('login.html', cfg=cfg, STATE=state)


# FACEBOOK LOG IN FUNCTION
# Here we will do the processing to authenticate with facebook
@app.route('/fbconnect', methods=['POST'])
def fbconnect():
    # first check for the state tokens if they are not the same fail it back
    if request.args.get('state') != login_session['state']:
        response = make_response(json.dumps('Invalid state parameter.'), 401)
        response.headers['Content-Type'] = 'application/json'
        return response

    access_token = request.data.decode()
    # now we loaded in our client secrets and send them to Facebook
    app_id = json.loads(open('fb_client_secrets.json', 'r').read())['web']['app_id']
    app_secret = json.loads(open('fb_client_secrets.json', 'r').read())['web']['app_secret']
    url = 'https://graph.facebook.com/oauth/access_token?grant_type=fb_exchange_token&client_id=' + app_id + '&client' \
        '_secret=' + app_secret + '&fb_exchange_token=' + access_token
    print(url)
    http_handler = httplib2.Http()
    result = json.loads((http_handler.request(url, 'GET')[1]).decode())

    if result.get('error') is not None:
        response = make_response(json.dumps(result.get('error')), 500)
        response.headers['Content-Type'] = 'application/json'
        return response
    # now we use the token to get user info from the API
    userinfo_url = "https://graph.facebook.com/v2.12/me"
    '''
            Due to the formatting for the result from the server token exchange we have to
            split the token first on commas and select the first index which gives us the key : value
            for the server access token then we split it on colons to pull out the actual token value
            and replace the remaining quotes with nothing so that it can be used directly in the graph
            api calls
    '''
    token = result['access_token']

    url = 'https://graph.facebook.com/v2.12/me?access_token=' + token + '&fields=name,id,email'
    result = http_handler.request(url, 'GET')[1]
    # for debugging print
    print("url sent for API Access", url)

    data = json.loads(result)
    login_session['provider'] = 'facebook'
    login_session['username'] = data['name']
    login_session['email'] = data['email']
    login_session['facebook_id'] = data['id']

    # while we are at it lets store the token (we also need to store it in order to log out ;) )
    login_session['access_token'] = token

    # we also want the users picture from facebook, that is from a separate call
    url2 = 'https://graph.facebook.com/v2.12/' + login_session['facebook_id'] + '/picture?redirect=false&access_token='+ login_session['access_token']
    print(url2)
    result = http_handler.request(url2, 'GET')[1]
    data = json.loads(result)
    login_session['picture'] = data["data"]["url"]

    # Now we have gathered all the pertinate user information lets see if they already exist before we add them
    user_id = get_user_id(login_session['email'])
    if not user_id:
        user_id = create_user(login_session)
    login_session['user_id'] = user_id

    # get ready to splash user info on page
    output = ''
    output += '<h1>Welcome, '
    output += login_session['username']
    output += '!</h1>'
    output += '<img src="'
    output += login_session['picture']
    output += ' " style = "width: 300px; height: 300px; border-radius: 150px; -webkit-border-radius: 150px; -moz-border-radius: 150px;">'

    flash("Now logged in as " + login_session['username'])
    return output


# Now we will make a disconnect function for facebook auth
@app.route('/fbdisconnect')
def fbdisconnect():
    facebook_id = login_session['facebook_id']
    access_token = login_session['access_token']
    url = 'https://graph.facebook.com/' + facebook_id + '/permissions?access_token=' + access_token
    http_handler = httplib2.Http()
    result = http_handler.request(url, 'DELETE')[1]
    print(result)
    return "You have bee logged out"


# GOOGLE LOG IN FUNCTION
# Here we will do all processing to authenticate with google
@app.route('/gconnect', methods=['POST'])
def gconnect():
    # Validate state token if they are not the same return back with unauthorized
    if request.args.get('state') != login_session['state']:
        # send a 401 http message back
        response = make_response(json.dumps('Invalid state parameter.'), 401)
        response.headers['Content-Type'] = 'application/json'
        return response
    # get authorization code
    code = request.data

    try:
        # we will now take the code and upgrade it into a credentials object
        oauth_flow = flow_from_clientsecrets('client_secrets.json', scope='')
        oauth_flow.redirect_uri = 'postmessage'
        credentials = oauth_flow.step2_exchange(code)
    # if there is something wrong throw an error
    except FlowExchangeError:
        response = make_response(
            json.dumps('failed to upgrade the authorization code.'), 401)
        response.headers['Content-Type'] = 'application/json'
        return response

    # Now we check that the access token is valid against google's api
    access_token = credentials.access_token
    url = ('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token=' + access_token)
    http_handler = httplib2.Http()
    # we will make an http GET request from google's api and load that into json
    result = json.loads((http_handler.request(url, 'GET')[1]).decode())
    # if there was an error in the access token info, abort out
    if result.get('error') is not None:
        response = make_response(json.dumps(result.get('error')), 500)
        response.headers['Content-Type'] = 'application/json'
        return response

    # now verify access token is used for the intended user
    google_id = credentials.id_token['sub']
    if result['user_id'] != google_id:
        response = make_response(json.dumps("Token's user ID doesn't match given user ID!"), 401)
        response.headers['Content-Type'] = 'application/json'
        return response

    # Verify that the access token is valid for this application.
    if result['issued_to'] != CLIENT_ID:
        response = make_response(json.dumps("Token's client ID does not match the app's!!"), 401)
        print("Token's Client ID does not match app's")
        response.headers['Content-Type'] = 'application/json'
        return response

    # Now we check if the user has already connected with session
    # These are a part of the Flask framework globals
    stored_access_token = login_session.get(access_token)
    stored_google_id = login_session.get('google_id')
    if stored_access_token is not None and google_id == stored_google_id:
        response = make_response(json.dumps('Current User is already connected.'), 200)
        response.headers['Content-Type'] = 'application/json'
        return response

    # Lets store these for later use
    login_session['provider'] = 'google'
    login_session['access_token'] = credentials.access_token
    login_session['google_id'] = google_id

    # now lets get the user info
    user_info_uri = "https://www.googleapis.com/oauth2/v1/userinfo"
    params = {'access_token': credentials.access_token, 'alt': 'json'}
    answer = requests.get(user_info_uri, params=params)

    # put the response data into a json object for use to use
    data = answer.json()

    login_session['username'] = data['name']
    login_session['picture'] = data['picture']
    login_session['email'] = data['email']

    # we can see if the user exists in our database, if it doesnt we add it
    user_id = get_user_id(login_session['email'])
    if not user_id:
        user_id = create_user(login_session)
    login_session['user_id'] = user_id

    # now that we have what we need lets display out what we found
    output = ''
    output += '<h1>Welcome, '
    output += login_session['username']
    output += '!</h1>'
    output += '<img src="'
    output += login_session['picture']
    output += ' " style = "width: 300px; height: 300px; border-radius: 150px; -webkit-border-radius: 150px; -moz-border-radius: 150px;">'
    flash("you are now logged in as " + login_session['username'])
    print("Done!")
    return output

# LOG OUT FUNCTION
# Now we need to have the functionality to disconnect effectively logging a user out
@app.route('/gdisconnect')
def gdisconnect():
    # Only disconnect a connected user
    # credentials = login_session.get('credentials')
    # if credentials is None:
    #   response = make_response(json.dumps("Current User not Connected!"), 401)
    #  response.headers['Content-Type'] = 'application/json'
    # return response
    print(login_session)
    access_token = login_session.get('access_token')
    # check if to see if that user is connected
    if access_token is None:
        print("Access token is none")
        response = make_response(json.dumps('Current User not connected.'), 401)
        response.headers['Content-type'] = 'application/json'
        return response
    print("in gdisconnect access token is " + access_token)
    print("User Name is: " + login_session['username'])

    url = 'https://accounts.google.com/o/oauth2/revoke?token=' + login_session['access_token']
    print(url)
    http_handler = httplib2.Http()
    result = http_handler.request(url, 'GET')[0]
    print("Result is ")
    print(result)

    # if we get an OK back we need to clear out the login_session
    if result['status'] == '200':
        del login_session['access_token']
        del login_session['google_id']
        del login_session['username']
        del login_session['email']
        del login_session['picture']
        response = make_response(json.dumps('Successfully disconnected.'), 200)
        response.headers['Content-type'] = 'application/json'
        print("done")
        return response
    else:
        response = make_response(json.dumps('Failed to revoke token for given user.'), 400)
        response.headers['Content-type'] = 'application/json'
        print("error")
        return response


# USER HELPER FUNCTIONS
# Create User
def create_user(login_session):
    new_user = User(name=login_session['username'], email=login_session['email'], picture=login_session['picture'])
    session.add(new_user)
    session.commit()
    user = session.query(User).filter_by(email=login_session['email']).one()
    return user.id


# get user info by id
def get_user_info(user_id):
    user = session.query(User).filter_by(id=user_id).one()
    return user


# get user id via email
def get_user_id(email):
    try:
        user = session.query(User).filter_by(email=email).one()
        return user.id
    except:
        return None


# JSON APIs to view Restaurant Information
@app.route('/restaurant/<int:restaurant_id>/menu/JSON')
def restaurantMenuJSON(restaurant_id):
    restaurant = session.query(Restaurant).filter_by(id=restaurant_id).one()
    items = session.query(MenuItem).filter_by(restaurant_id=restaurant_id).all()
    return jsonify(MenuItems=[i.serialize for i in items])


@app.route('/restaurant/<int:restaurant_id>/menu/<int:menu_id>/JSON')
def menuItemJSON(restaurant_id, menu_id):
    Menu_Item = session.query(MenuItem).filter_by(id=menu_id).one()
    return jsonify(Menu_Item=Menu_Item.serialize)


@app.route('/restaurant/JSON')
def restaurantsJSON():
    restaurants = session.query(Restaurant).all()
    return jsonify(restaurants=[r.serialize for r in restaurants])


# PUBLIC PAGE
# Show all restaurants
@app.route('/')
@app.route('/restaurant/')
def showRestaurants():
    restaurants = session.query(Restaurant).order_by(asc(Restaurant.name))
    if 'username' not in login_session:
        return render_template('publicrestaurants.html', restaurants=restaurants)
    else:
        return render_template('restaurants.html', restaurants=restaurants)


# Private Page
# Create a new restaurant
@app.route('/restaurant/new/', methods=['GET', 'POST'])
def newRestaurant():
    if 'username' not in login_session:
        return redirect('/login')
    if request.method == 'POST':
        newRestaurant = Restaurant(name=request.form['name'], user_id=login_session['user_id'])
        session.add(newRestaurant)
        flash('New Restaurant %s Successfully Created' % newRestaurant.name)
        session.commit()
        return redirect(url_for('showRestaurants'))
    else:
        return render_template('newRestaurant.html')


# Private Page
# Edit a restaurant
@app.route('/restaurant/<int:restaurant_id>/edit/', methods = ['GET', 'POST'])
def editRestaurant(restaurant_id):
    editedRestaurant = session.query(Restaurant).filter_by(id=restaurant_id).one()
    if 'username' not in login_session:
        return redirect('/login')
    if editedRestaurant.user_id != login_session['user_id']:
        return "<script>function alertFunction() {alert('You are not authorized to edit this restaurant. Please create" \
               "your own restaurant in order to edit!');}</script><body onload='alertFunction()''>"
    if request.method == 'POST':
        if request.form['name']:
            editedRestaurant.name = request.form['name']
            flash('Restaurant Successfully Edited %s' % editedRestaurant.name)
            return redirect(url_for('showRestaurants'))
    else:
        return render_template('editRestaurant.html', restaurant = editedRestaurant)


# Private Page
# Delete a restaurant
@app.route('/restaurant/<int:restaurant_id>/delete/', methods=['GET', 'POST'])
def deleteRestaurant(restaurant_id):
    restaurantToDelete = session.query(Restaurant).filter_by(id=restaurant_id).one()
    if 'username' not in login_session:
        return redirect('/login')
    if restaurantToDelete.user_id != login_session['user_id']:
        return "<script>function alertFunction() {alert('You are not authorized to delete this restaurant!" \
               "');}</script><body onload='alertFunction()''>"
    if request.method == 'POST':
        session.delete(restaurantToDelete)
        flash('%s Successfully Deleted' % restaurantToDelete.name)
        session.commit()
        return redirect(url_for('showRestaurants', restaurant_id=restaurant_id))
    else:
        return render_template('deleteRestaurant.html', restaurant=restaurantToDelete)


# PUBLIC PAGE
# Show a restaurant menu
@app.route('/restaurant/<int:restaurant_id>/')
@app.route('/restaurant/<int:restaurant_id>/menu/')
def showMenu(restaurant_id):
    restaurant = session.query(Restaurant).filter_by(id=restaurant_id).one()
    creator = get_user_info(restaurant.user_id)
    items = session.query(MenuItem).filter_by(restaurant_id=restaurant_id).all()
    if 'username' not in login_session or creator.id != login_session['user_id']:
        return render_template('publicmenu.html', items=items, restaurant=restaurant, creator=creator)
    else:
        return render_template('menu.html', items=items, restaurant=restaurant, creator=creator)
     

# Private Page
# Create a new menu item
@app.route('/restaurant/<int:restaurant_id>/menu/new/', methods=['GET', 'POST'])
def newMenuItem(restaurant_id):
    if 'username' not in login_session:
        return redirect('/login')
    restaurant = session.query(Restaurant).filter_by(id=restaurant_id).one()
    if login_session['user_id'] != restaurant.user_id:
        return "<script>function alertFunction() {alert('You are not authorized to add menu items to this restaurant!" \
               "Please create your own restaurant and then add items to it.');}</script><body onload='alertFunction()''>"
    if request.method == 'POST':
        newItem = MenuItem(name=request.form['name'], description=request.form['description'],
                           price=request.form['price'], course=request.form['course'], restaurant_id=restaurant_id,
                           user_id=restaurant.user_id)
        session.add(newItem)
        session.commit()
        flash("New Menu " + newItem.name + "Item Successfully Created")
        return redirect(url_for('showMenu', restaurant_id=restaurant_id))
    else:
        return render_template('newmenuitem.html', restaurant_id=restaurant_id)


# Private Page
# Edit a menu item
@app.route('/restaurant/<int:restaurant_id>/menu/<int:menu_id>/edit', methods=['GET', 'POST'])
def editMenuItem(restaurant_id, menu_id):
    if 'username' not in login_session:
        return redirect('/login')
    editedItem = session.query(MenuItem).filter_by(id=menu_id).one()
    restaurant = session.query(Restaurant).filter_by(id=restaurant_id).one()
    if login_session['user_id'] != restaurant.user_id:
        return "<script>function alertFunction() {alert('You are not authorized to edit menu items to this restaurant!" \
                "Please create your own restaurant and then edit items to it.');}</script><body onload='alertFunction()''>"
    if request.method == 'POST':
        if request.form['name']:
            editedItem.name = request.form['name']
        if request.form['description']:
            editedItem.description = request.form['description']
        if request.form['price']:
            editedItem.price = request.form['price']
        if request.form['course']:
            editedItem.course = request.form['course']
        session.add(editedItem)
        session.commit() 
        flash('Menu Item Successfully Edited')
        return redirect(url_for('showMenu', restaurant_id=restaurant_id))
    else:
        return render_template('editmenuitem.html', restaurant_id=restaurant_id, menu_id=menu_id, item=editedItem)


# Private Page
# Delete a menu item
@app.route('/restaurant/<int:restaurant_id>/menu/<int:menu_id>/delete', methods=['GET', 'POST'])
def deleteMenuItem(restaurant_id, menu_id):
    if 'username' not in login_session:
        return redirect('/login')
    restaurant = session.query(Restaurant).filter_by(id=restaurant_id).one()
    itemToDelete = session.query(MenuItem).filter_by(id=menu_id).one()
    if login_session['user_id'] != restaurant.user_id:
        return "<script>function alertFunction() {alert('You are not authorized to delete menu items to this restaurant!" \
               "Please create your own restaurant and then add items to it.');}</script><body onload='alertFunction()''>"
    if request.method == 'POST':
        session.delete(itemToDelete)
        session.commit()
        flash('Menu Item Successfully Deleted')
        return redirect(url_for('showMenu', restaurant_id=restaurant_id))
    else:
        return render_template('deleteMenuItem.html', item=itemToDelete)


# Create a Generic Disconnect function to scale for more than one OAuth Provider
@app.route('/disconnect')
def disconnect():
    if 'provider' in login_session:
        if login_session['provider'] == 'google':
            gdisconnect()
            # del login_session['google_id']
            # del login_session['access_token']
        if login_session['provider'] == 'facebook':
            del login_session['facebook_id']
        del login_session['username']
        del login_session['email']
        del login_session['picture']
        del login_session['user_id']
        del login_session['provider']
        flash("You have been successfully logged out!")
        return redirect(url_for('showRestaurants'))
    else:
        flash("You were not logged in!!")
        return redirect(url_for('showRestaurants'))


if __name__ == '__main__':
    app.secret_key = 'super_secret_key'
    app.debug = True
    app.run(host='0.0.0.0', port=9000)
