#!/usr/bin/env python

import os
from flask import Flask, g, abort, jsonify, render_template
from time import time, strftime
from functools import wraps
from flask import request, send_from_directory, redirect, url_for, Response
from werkzeug.routing import BaseConverter
from passlib.hash import sha256_crypt
from itsdangerous import (TimedJSONWebSignatureSerializer
  as Serializer, BadSignature, SignatureExpired)
import requests

# initialization
base = os.path.dirname(os.path.realpath(__file__))

app = Flask(__name__)
app.config['SECRET_KEY'] = 'h&#2Fvi9>4@_Cw/j/*)\_K81~.dN$f5!r%<Z$63|;1l=_'
app.config['METADATA_URL'] = 'http://192.168.92.7:8084/mtpublic/%s'
app.config['TOKEN_URL'] = 'http://192.168.92.7:8084/mtpublic/_design/clients/_view/all/?key="%s"'

class RegexConverter(BaseConverter):
  def __init__(self, url_map, *items):
    super(RegexConverter, self).__init__(url_map)
    self.regex = items[0]

app.url_map.converters['regex'] = RegexConverter

def check_auth(username, password=None, hash=None):
  """Check if (username, password) or (username, hash) is valid."""
  print "Checking auth...", username, password, hash
  try:
#    resp = requests.get(app.config['TOKEN_URL'] % username, timeout=3)
    digest = sha256_crypt.encrypt(password) if password else hash
#    if resp.ok and \
#       len(resp.json()['rows'])==1 and \
#       sha256_crypt.verify(str(resp.json()['rows'][0]['value']['password']), digest):
#      return generate_token(str(resp.json()['rows'][0]['value']['_id']), username, digest)
    if username=="sohrabjk" and sha256_crypt.verify('nnch', digest):
      return generate_token(1, username, digest)
    return None
  except:
    return None

def generate_token(id, username, hash, expiration=600):
  s = Serializer(app.config['SECRET_KEY'], expires_in=expiration)
  return s.dumps({'id': id, 'username': username, 'hash': hash})

def untoken(token, headers=False):
  s = Serializer(app.config['SECRET_KEY']);
  (token, header) = s.loads(token if token is not None else "", return_header=True)
  return (token, header) if headers else token

def check_token(token):
  print "Checking token..."
  try:
    s = Serializer(app.config['SECRET_KEY']);
    (token, header) = untoken(token, True)
    if header['exp'] - int(time()) < 60:
     return check_auth(token['username'], hash=token['hash'])
    return generate_token(token['id'], token['username'], token['hash'])
  except SignatureExpired:
    print "Signature expired"
    return False  # Valid token, but expired
  except BadSignature:
    print "Bad signature"
    return False  # Invalid token

def authenticate():
  """401 Response with Basic Auth"""
  return Response(
  'Could not verify your access level for that URL.\n'
  'You have to login with proper credentials', 401,
  {'WWW-Authenticate': 'Basic realm=""'})

def requires_auth(f):
  @wraps(f)
  def decorated(*args, **kwargs):
    auth = request.authorization
    print 'Authorization: ', auth
    token = check_token(request.cookies.get('token')) or \
      (auth and check_auth(auth.username, auth.password))
    if token:
      g._token = token
      return f(*args, **kwargs)
    else:
      return authenticate()
  return decorated

@app.before_request
def before_request():
  g.start = time()

@app.after_request
def apply_caching(response):
  if g.get('_token', None):
    response.set_cookie('token', value=g._token, max_age=600)
  else:
    response.set_cookie('token', value='')
  if g.get('start', None):
    response.headers['X-Runtime'] = "%1.1f ms" % ((time() - g.start)*1000)
  response.headers['Cache-Control'] = "max-age=0, no-cache, no-store, must-revalidate"
  response.headers['Expires'] = "Thu, 29 Nov 1984 19:40:00"
  response.headers['Pragma'] = "no-cache"
  response.headers['Run-in'] = os.path.dirname(os.path.abspath(__file__))
  response.headers['CWD'] = os.path.dirname(os.getcwd())

  return response

@app.route('/logout')
@requires_auth
def logout():
  if g.get('_token', None):
    g._token = None
  return redirect(url_for('index'), 302)

@app.route('/tracking/<identifier>')
#@requires_auth
def index(identifier):
  return render_template('layout.html', identifier=identifier)

@app.route('/id/<identifier>')
def identification(identifier):
  return render_template('identification.html', identifier=identifier)

@app.route('/stocks/')
def query_stocks():
  from json import dumps
  json = dumps([
      { "id": 1, "description": "STOCK PRINCIPAL", "default": True },
      { "id": 2, "description": "STOCK SECONDAIRE", "default": False },
      { "id": 3, "description": "EN COURS OF", "default": False },
      { "id": 4, "description": "STOCK SAV", "default": False },
      { "id": 5, "description": "STOCK VENTE COMPOSANTS", "default": False },
      { "id": 6, "description": "STOCK ENTREE", "default": False },
      { "id": 7, "description": "STOCK RETOUR", "default": False },
      { "id": 8, "description": "STOCK RD", "default": False },
      { "id": 9, "description": "STOCK TRANSIT", "default": False },
      { "id": 10, "description": "STOCK DEMO-OCCASION", "default": False }])

  return Response(json, mimetype='application/json')

@app.route('/article/<article>')
def query_article(article):
  import shlex, subprocess
  debug = False
  debug_xml = """<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<articles count="1">
<article identifier="2055159000" value="11.060" currency="CHF" active="1" stocks="70">
<description lang="FRE">PAIRE DE GANTS POUR NAGRA HI-FI</description>
<stock id="1" quantity="70" location="233-A-03"/></article>
</articles>
"""

#  if len(identifier) != 10:
#    return abort(404)
  shell = """mysql -u F5VksfQsYW -h 192.168.10.10 -pmWaq9FiGC6\
  --default-character-set=utf8\
  --skip-column-names\
  --raw\
  --execute="CALL article_query('%s', 'FRE')"\
  dfnesa
  """ % article
  args = shlex.split(shell)
  output = subprocess.check_output(args) if not debug else debug_xml

  import xml.etree.ElementTree as ET
  root = ET.fromstring(output)

  try:
    out = {
      "identifier": root[0].attrib['identifier'],
      "value": root[0].attrib['value'],
      "currency": root[0].attrib['currency'],
      "quantity": root[0].attrib['stocks'],
      "description": root[0][0].text,
      "stocks": []
    }
    for stock in root[0].findall('stock'):
      out['stocks'].append({
        "id": stock.attrib['id'],
        "quantity": stock.attrib['quantity'],
        "location": stock.attrib['location']
      })
    return jsonify(out)
  except:
    abort(404)

#  return jsonify({ "identifier": article,
#    "description": "CABLE USB AVEC MINI CONNECTEUR USB",
#    "value": 194.95, "currency": "CHF",
#    "stocks": [
#     { "id": 1, "description": "PRINCIPAL", "quantity": 127, "inventories": [
#        { "id": 1249, "inventoriedOn": "2016-11-10 15:01:05", "quantity": 65, "location": "239-A-45" },
#        { "id": 1250, "inventoriedOn": "2016-11-10 15:04:14", "quantity": 30, "location": "233-F-18" },
#        { "id": 1251, "inventoriedOn": "2016-11-10 15:11:49", "quantity": 35, "location": "379-G-10" }
#      ] },
#     { "id": 2, "description": "RESERVE", "quantity": 5, "inventories": [] }
#    ] },
#    sort_keys=True)

def to_xml(inventory, source):
  import json
  import xml.etree.ElementTree as ET

  def ifnull(key, el, default):
    return default if key not in el else el[key]

  def as_xml(position):
    article = position['article']
    createdBy = {}
    createdBy['id'] = source
    p = ET.Element('position')
    p.set('source', source)
    p.set('active', ifnull('active', position, 'true'))
    p.set('uuid', position['uuid'])
    p.set('createdOn', position['createdOn'])
    p.set('createdBy', ifnull('createdBy', position, createdBy)['id'])
    p.set('article', str(article['identifier']))
    p.set('stock', str(ifnull('stock', position, '')))
    p.set('location', str(ifnull('location', position, '')))
    p.set('quantity', str(ifnull('quantity', position, '')))
    p.text = article['description']
    return p

  root = ET.Element('inventory')
  for position in inventory:
    p = as_xml(position)
    root.append(p)

  return ET.ElementTree(root)

@app.route('/user/<user_id>')
def query_user(user_id=0):
  import shlex, subprocess

#  if len(identifier) != 10:
#    return abort(404)
  shell = """mysql -u F5VksfQsYW -h 192.168.10.10 -pmWaq9FiGC6\
  --default-character-set=utf8\
  --skip-column-names\
  --raw\
  --execute="CALL user_query('%s')"\
  dfnesa
  """ % user_id
  args = shlex.split(shell)
  output = subprocess.check_output(args)

  import xml.etree.ElementTree as ET
  root = ET.fromstring(output)

  try:
    if not int(root.attrib['count'])==1:
      raise LookupError('User count is not equal to 1')
    out = {
      "id": root[0].attrib['id'],
      "username": root[0].attrib['username'],
      "role": root[0].attrib['role'],
      "active": root[0].attrib['active']=="1",
      "lang": root[0].attrib['lang'],
      "visa": root[0].attrib['visa'],
      "admin": root[0].attrib['admin']=="1",
      "description": root[0].text
    }

    from time import sleep
    from random import uniform
    sleep(uniform(0,3) if uniform(0,1) < 0.25 else 0)
    return jsonify(out)
  except:
    abort(404)

#  return jsonify({ "identifier": article,
#    "description": "CABLE USB AVEC MINI CONNECTEUR USB",
#    "value": 194.95, "currency": "CHF",
#    "stocks": [
#     { "id": 1, "description": "PRINCIPAL", "quantity": 127, "inventories": [
#        { "id": 1249, "inventoriedOn": "2016-11-10 15:01:05", "quantity": 65, "location": "239-A-45" },
#        { "id": 1250, "inventoriedOn": "2016-11-10 15:04:14", "quantity": 30, "location": "233-F-18" },
#        { "id": 1251, "inventoriedOn": "2016-11-10 15:11:49", "quantity": 35, "location": "379-G-10" }
#      ] },
#     { "id": 2, "description": "RESERVE", "quantity": 5, "inventories": [] }
#    ] },
#    sort_keys=True)

@app.route("/id/<string:identifier>/", methods=['POST'])
def post_inv(identifier):
  import json
  payload = json.loads(request.data)

  subdir = ['commits'] + [identifier] + [strftime("%Y%m%d")]
  directory = os.path.join(base, *subdir)
  if not os.path.exists(directory):
    os.makedirs(directory)

  filename = "commit_%s.json" % (strftime("%H%M%S"))
  file = open(os.path.join(directory, filename), "wb")
  file.write(request.data)
  file.close()

  xml = to_xml(payload, identifier)
  filename = "commit_%s.xml" % (strftime("%H%M%S"))
  file = open(os.path.join(directory, filename), "wb")
  xml.write(file, encoding="UTF-8", xml_declaration=True)
  file.close()

  import subprocess, shlex
  shell = """%s -i -o -w %s""" % (os.path.join(base, 'sql/xml2sql.sh'), os.path.join(directory, filename))
  subprocess.Popen((shlex.split(shell)))

  return "Ahoy, capt'n"


@app.route('/token', methods=['GET'])
@requires_auth
def token():
  return jsonify({'id': untoken(g._token)['id'] })

@app.route('/metadata/<id>', methods=['GET'])
@requires_auth
def metadata(id):
  if id!=untoken(g._token)['id']:
    abort(403)
  resp = requests.get(app.config['METADATA_URL'] % id, timeout=3)
  if resp.ok:
    return jsonify(resp.json())
  else:
    abort(500)

@app.route("/list/<int:list_type>/<regex('([A-Z0-9]{7,10})'):identifier>/", methods=['GET'])
@app.route("/list/<int:list_type>/<regex('([A-Z0-9]{7,10})'):identifier>/<regex('(\d{4}-\d{2}-\d{2})'):date_from>/<regex('(\d{4}-\d{2}-\d{2})'):date_until>/", methods=['GET'])
def sample_data(list_type, identifier, date_from=None, date_until=None):
  import os, shlex

  def ifnull(var, val):
    if var is None:
      return val
    return var

  shell = "xslt/fetch.sh %s %s %s %s" % (list_type, identifier, ifnull(date_from, ''), ifnull(date_until, ''))
  os.system(os.path.join(os.path.dirname(__file__), shell))

  return send_from_directory(os.path.join(os.path.dirname(__file__), 'xslt'), filename='mt_batches_summary2.json')

@app.route("/<regex('(font|fonts)'):head>/<resource>")
def get_images(head, resource):
  return redirect(url_for('static', filename='fonts/'+resource))

###
# Proxy wrapper class to allow Flask to run behind a reverse
# proxy (such as Apache)
#
class ReverseProxied(object):
  def __init__(self, app):
    self.app = app

  def __call__(self, environ, start_response):
    script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
    if script_name:
      environ['SCRIPT_NAME'] = script_name
      path_info = environ['PATH_INFO']
      if path_info.startswith(script_name):
        environ['PATH_INFO'] = path_info[len(script_name):]

    scheme = environ.get('HTTP_X_SCHEME', '')
    if scheme:
      environ['wsgi.url_scheme'] = scheme
    return self.app(environ, start_response)

app.wsgi_app = ReverseProxied(app.wsgi_app)

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