# -*- encoding: utf-8 -*-

import os
import random
import string
import sys
from datetime import datetime

import click
from flask import Flask, request, render_template, redirect, url_for, abort, send_from_directory
from flask_login import LoginManager, current_user, login_user
from sqlalchemy import Column, DateTime, Integer, String, create_engine
import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import scoped_session, sessionmaker
from functools import wraps


login_manager = LoginManager()

app = Flask('grader')
app.config.from_pyfile('config.py')
if 'FACEWORKS_GRADER_CONFIG' in os.environ:
    app.config.from_envvar('FACEWORKS_GRADER_CONFIG')

login_manager.init_app(app)
engine = create_engine(app.config.get['SQL_URL'], convert_unicode=True)
db_session = scoped_session(sessionmaker(autocommit=False,
                                         autoflush=False,
                                         bind=engine))
Base = declarative_base()
# Model Layer #


class User(Base):
    __tablename__ = 'users'
    id = Column(Integer, primary_key=True)
    name = Column(String(50), unique=True)
    pwd = Column(String(120))
    progress = Column(Integer)

    def __init__(self, name=None):
        self.name = name
        self.pwd = ''.join(random.choices(string.ascii_letters, k=8))
        self.progress = 0

    def __repr__(self):
        return '<User %d: %r>' % (self.id, self.name)

    def is_authenticated(self):
        return True

    def is_active(self):
        return True

    def is_anonymous(self):
        return False

    def get_id(self):
        return str(self.id)


class Image(Base):
    __tablename__ = 'images'
    id = Column(Integer, primary_key=True)
    md5 = Column(String(32), unique=True)
    count = Column(Integer)

    def __init__(self, md5=None):
        self.md5 = md5
        self.count = 0

    def __repr__(self):
        return '<Image %d: %r>' % (self.id, self.md5)


class Score(Base):
    __tablename__ = 'scores'
    id = Column(Integer, primary_key=True)
    user = Column(Integer)
    image = Column(Integer)
    score = Column(Integer)
    ts = Column(DateTime)
    nouce = Column(Integer)

    def __init__(self, user, image, score=0):
        self.user = user
        self.image = image
        self.score = score
        self.ts = datetime.utcnow()
        self.nouce = random.randint(0, 10000)

    def __repr__(self):
        return '<Score %d->%d, %d>' % (self.user, self.image, self.score)


@login_manager.user_loader
def load_user(user_id):
    return db_session.query(User).filter(User.id == int(user_id)).one_or_none()

# Logic Layer #


def _init_db():
    Base.metadata.create_all(bind=engine)


def _add_score(user, image):
    # add score records
    score = Score(user, image)
    db_session.add(score)
    db_session.commit()


def _add_image(md5):
    # add to every user
    image = Image(md5=md5)
    db_session.add(image)
    db_session.commit()
    for row in db_session.query(User.id):
        score = Score(row.id, image.id)
        db_session.add(score)
    db_session.commit()
    return image


def _add_user(name):
    # add every image
    user = User(name)
    db_session.add(user)
    db_session.commit()
    for row in db_session.query(Image.id):
        score = Score(user.id, row.id)
        db_session.add(score)
    db_session.commit()
    return user


def _put_score(user, image, score):
    # put score
    ins = db_session.query(Score).filter(
        Score.user == user, Score.image == image).one()
    if score != 0 and ins.score == 0:
        image = db_session.query(Image).filter(Image.id == image).one()
        image.count += 1
        db_session.add(image)
    ins.score = score
    ins.ts = datetime.utcnow()
    db_session.add(ins)
    db_session.commit()


def _select_image(user):
    # Select a image for a user
    return db_session.query(Score).filter(Score.user == user, Score.score == 0).order_by(Score.nouce).first()


def _get_user_process(user):
    total = db_session.query(sqlalchemy.func.count(
        Score.id)).filter(Score.user == user)
    zeros = db_session.query(sqlalchemy.func.count(Score.id)).filter(
        Score.user == user, Score.score == 0)
    return total[0][0], zeros[0][0]


def _get_sys_process():
    total = db_session.query(sqlalchemy.func.count(Image.id))
    zeros = db_session.query(
        sqlalchemy.func.count(Image.id)).filter(Image.count < 3)
    return total[0][0], zeros[0][0]


# View Layer #

def login_required(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        if not current_user.is_authenticated:
            return redirect('/login')
        else:
            return func(*args, **kwargs)
    return wrapper


@app.route('/')
def index_view():
    if not current_user.is_authenticated:
        return redirect('/login')
    else:
        return redirect('/score')


@app.route('/login', methods=['GET', 'POST'])
def login_view():
    if request.method == 'GET':
        if current_user.is_anonymous:
            return render_template('login.html')
        else:
            return redirect('/score')
    else:
        username = request.form.get('username')
        pwd = request.form.get('pwd')
        if not pwd or not username:
            abort(400)
        r = db_session.query(User).filter(
            User.name == username, User.pwd == pwd).one_or_none()
        if not r:
            abort(403)
        if r.name == username:
            login_user(r)
            return redirect('/score')
        else:
            abort(400)


@app.route('/score', methods=['GET'])
@login_required
def score_route_view():
    next_image = _select_image(int(current_user.get_id()))
    if not next_image:
        return redirect('/finished')
    return redirect('/score/%s' % next_image.id)


@app.route('/score/<vid>', methods=['GET', 'POST'])
@login_required
def score_view(vid):
    if request.method == 'GET':
        image = db_session.query(Image).filter(
            Image.id == int(vid)).one_or_none()
        if not image:
            return redirect('/score')
        return render_template('score.html', image_url='/images/' + image.md5,
                               sys_process=_get_sys_process(),
                               user_process=_get_user_process(int(current_user.get_id())))
    else:
        score = request.form.get('score')
        if score == None:
            abort(405)
        score = int(score)
        _put_score(int(current_user.get_id()), int(vid), score=score)
        return redirect('/score')


@app.route('/finished', methods=['GET'])
@login_required
def finish_view():
    return render_template('finished.html', sys_process=_get_sys_process())


@app.route('/images/<filename>')
def image_view(filename):
    return send_from_directory('images', filename)

# Command Layer #


@click.group()
def cli():
    pass


@cli.command()
def run():
    app.run('localhost', 5000, debug=True)


@cli.command()
@click.argument('name')
def add_user(name):
    user = _add_user(name)
    print("New User %s, password is %s" % (user.name, user.pwd))


@cli.command()
@click.argument('path', nargs=-1)
def add_image(path):
    for i in path:
        name = os.path.basename(i)
        _add_image(name)


@cli.command()
def init_db():
    _init_db()


def main():
    cli()

if __name__ == '__main__':
    main()