import os
import shutil
import random
import subprocess
import platform
import git
import mimetypes
import magic
import flask
import cairosvg
from functools import wraps
from datetime import datetime
from enum import Enum
from cairosvg import svg2png
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from markupsafe import escape, Markup
from flask_migrate import Migrate
from PIL import Image
from flask_httpauth import HTTPBasicAuth
from celery import Celery, Task
import config
import celeryIntegration

app = flask.Flask(__name__)
app.config.from_mapping(
    CELERY=dict(
        broker_url=config.REDIS_URI,
        result_backend=config.REDIS_URI,
        task_ignore_result=True,
    ),
)
worker = celeryIntegration.initCeleryApp(app)

auth = HTTPBasicAuth()

app.config["SQLALCHEMY_DATABASE_URI"] = config.DB_URI
app.config["SECRET_KEY"] = config.DB_PASSWORD
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
migrate = Migrate(app, db)
from models import *

import celeryTasks


def gitCommand(repo, data, *args):
    if not os.path.isdir(repo):
        raise FileNotFoundError("Repo not found")
    env = os.environ.copy()

    command = ["git", *args]

    proc = subprocess.Popen(" ".join(command), cwd=repo, env=env, shell=True, stdout=subprocess.PIPE,
                            stdin=subprocess.PIPE)
    print(command)

    if data:
        proc.stdin.write(data)

    out, err = proc.communicate()
    return out


def onlyChars(string, chars):
    for i in string:
        if i not in chars:
            return False
    return True


def getPermissionLevel(loggedIn, username, repository):
    user = User.query.filter_by(username=loggedIn).first()
    repo = Repo.query.filter_by(route=f"/{username}/{repository}").first()

    if user and repo:
        permission = RepoAccess.query.filter_by(user=user, repo=repo).first()
        if permission:
            return permission.accessLevel

    return None


def getVisibility(username, repository):
    repo = Repo.query.filter_by(route=f"/{username}/{repository}").first()

    if repo:
        return repo.visibility

    return None


def getFavourite(loggedIn, username, repository):
    print(loggedIn, username, repository)
    relationship = RepoFavourite.query.filter_by(userUsername=loggedIn, repoRoute=f"/{username}/{repository}").first()
    return relationship


import gitHTTP
import jinjaUtils


def humanSize(value, decimals=2, scale=1024,
              units=("B", "kiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB", "RiB", "QiB")):
    for unit in units:
        if value < scale:
            break
        value /= scale
    if int(value) == value:
        # do not return decimals, if the value is already round
        return int(value), unit
    return round(value * 10 ** decimals) / 10 ** decimals, unit


def guessMIME(path):
    if os.path.isdir(path):
        mimetype = "inode/directory"
    elif magic.from_file(path, mime=True):
        mimetype = magic.from_file(path, mime=True)
    else:
        mimetype = "application/octet-stream"
    return mimetype


def convertToHTML(path):
    with open(path, "r") as f:
        contents = f.read()
    return contents


repositories = flask.Blueprint("repository", __name__, template_folder="templates/repository/")


@app.context_processor
def default():
    username = flask.session.get("username")

    userObject = User.query.filter_by(username=username).first()

    return {
        "loggedInUser": username,
        "userObject": userObject,
        "Notification": Notification,
        "unread": UserNotification.query.filter_by(userUsername=username).filter(UserNotification.attentionLevel > 0).count()
    }


@app.route("/")
def main():
    return flask.render_template("home.html")


@app.route("/about/")
def about():
    return flask.render_template("about.html", platform=platform)


@app.route("/settings/", methods=["GET", "POST"])
def settings():
    if not flask.session.get("username"):
        flask.abort(401)
    if flask.request.method == "GET":
        user = User.query.filter_by(username=flask.session.get("username")).first()

        return flask.render_template("user-settings.html", user=user)
    else:
        user = User.query.filter_by(username=flask.session.get("username")).first()

        user.displayName = flask.request.form["displayname"]
        user.URL = flask.request.form["url"]
        user.company = flask.request.form["company"]
        user.companyURL = flask.request.form["companyurl"]
        user.location = flask.request.form["location"]
        user.showMail = flask.request.form.get("showmail", user.showMail)

        db.session.commit()

        flask.flash(Markup("<iconify-icon icon='mdi:check'></iconify-icon>Settings saved"), category="success")
        return flask.redirect(f"/{flask.session.get('username')}", code=303)


@app.route("/favourites/", methods=["GET", "POST"])
def favourites():
    if not flask.session.get("username"):
        flask.abort(401)
    if flask.request.method == "GET":
        relationships = RepoFavourite.query.filter_by(userUsername=flask.session.get("username"))

        return flask.render_template("favourites.html", favourites=relationships)


@app.route("/notifications/", methods=["GET", "POST"])
def notifications():
    if not flask.session.get("username"):
        flask.abort(401)
    if flask.request.method == "GET":
        return flask.render_template("notifications.html", notifications=UserNotification.query.filter_by(userUsername=flask.session.get("username")))


@app.route("/accounts/", methods=["GET", "POST"])
def login():
    if flask.request.method == "GET":
        return flask.render_template("login.html")
    else:
        if "login" in flask.request.form:
            username = flask.request.form["username"]
            password = flask.request.form["password"]

            user = User.query.filter_by(username=username).first()

            if user and bcrypt.check_password_hash(user.passwordHashed, password):
                flask.session["username"] = user.username
                flask.flash(
                    Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged in as {username}"),
                    category="success")
                return flask.redirect("/", code=303)
            elif not user:
                flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>User not found"),
                            category="alert")
                return flask.render_template("login.html")
            else:
                flask.flash(Markup("<iconify-icon icon='mdi:account-question'></iconify-icon>Invalid password"),
                            category="error")
                return flask.render_template("login.html")
        if "signup" in flask.request.form:
            username = flask.request.form["username"]
            password = flask.request.form["password"]
            password2 = flask.request.form["password2"]
            email = flask.request.form.get("email")
            email2 = flask.request.form.get("email2")  # repeat email is a honeypot
            name = flask.request.form.get("name")

            if not onlyChars(username, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"):
                flask.flash(Markup(
                    "<iconify-icon icon='mdi:account-error'></iconify-icon>Usernames may only contain Latin alphabet, numbers, '-' and '_'"),
                            category="error")
                return flask.render_template("login.html")

            if username in config.RESERVED_NAMES:
                flask.flash(
                    Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>Sorry, {username} is a system path"),
                    category="error")
                return flask.render_template("login.html")

            userCheck = User.query.filter_by(username=username).first()
            if userCheck:
                flask.flash(
                    Markup(f"<iconify-icon icon='mdi:account-error'></iconify-icon>The username {username} is taken"),
                    category="error")
                return flask.render_template("login.html")

            if password2 != password:
                flask.flash(Markup("<iconify-icon icon='mdi:key-alert'></iconify-icon>Make sure the passwords match"),
                            category="error")
                return flask.render_template("login.html")

            user = User(username, password, email, name)
            db.session.add(user)
            db.session.commit()
            flask.session["username"] = user.username
            flask.flash(Markup(
                f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully created and logged in as {username}"),
                        category="success")
            return flask.redirect("/", code=303)


@app.route("/newrepo/", methods=["GET", "POST"])
def newRepo():
    if not flask.session.get("username"):
        flask.abort(401)
    if flask.request.method == "GET":
        return flask.render_template("new-repo.html")
    else:
        name = flask.request.form["name"]
        visibility = int(flask.request.form["visibility"])

        if not onlyChars(name, "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"):
            flask.flash(Markup(
                    "<iconify-icon icon='mdi:error'></iconify-icon>Repository names may only contain Latin alphabet, numbers, '-' and '_'"),
                    category="error")
            return flask.render_template("new-repo.html")

        user = User.query.filter_by(username=flask.session.get("username")).first()

        repo = Repo(user, name, visibility)
        db.session.add(repo)
        db.session.commit()

        if not os.path.exists(os.path.join(config.REPOS_PATH, repo.route)):
            subprocess.run(["git", "init", repo.name],
                           cwd=os.path.join(config.REPOS_PATH, flask.session.get("username")))

        flask.flash(Markup(f"<iconify-icon icon='mdi:folder'></iconify-icon>Successfully created repository {name}"),
                    category="success")
        return flask.redirect(repo.route, code=303)


@app.route("/logout")
def logout():
    flask.session.clear()
    flask.flash(Markup(f"<iconify-icon icon='mdi:account'></iconify-icon>Successfully logged out"), category="info")
    return flask.redirect("/", code=303)


@app.route("/<username>/", methods=["GET", "POST"])
def userProfile(username):
    oldRelationship = UserFollow.query.filter_by(followerUsername=flask.session.get("username"), followedUsername=username).first()
    if flask.request.method == "GET":
        user = User.query.filter_by(username=username).first()
        match flask.request.args.get("action"):
            case "repositories":
                repos = Repo.query.filter_by(ownerName=username, visibility=2)
                return flask.render_template("user-profile-repositories.html", user=user, repos=repos, relationship=oldRelationship)
            case "followers":
                return flask.render_template("user-profile-followers.html", user=user, relationship=oldRelationship)
            case "follows":
                return flask.render_template("user-profile-follows.html", user=user, relationship=oldRelationship)
            case _:
                return flask.render_template("user-profile-overview.html", user=user, relationship=oldRelationship)

    elif flask.request.method == "POST":
        match flask.request.args.get("action"):
            case "follow":
                if username == flask.session.get("username"):
                    flask.abort(403)
                if oldRelationship:
                    db.session.delete(oldRelationship)
                else:
                    relationship = UserFollow(
                            flask.session.get("username"),
                            username
                    )
                    db.session.add(relationship)

                    user = db.session.get(User, username)
                    author = db.session.get(User, flask.session.get("username"))
                    notification = Notification({"type": "update", "version": "0.0.0"})
                    db.session.add(notification)
                    db.session.commit()

                    result = celeryTasks.sendNotification.delay(notification.id, [username], 1)
                    flask.flash(f"Sending notification in task {result.id}", "success")

                db.session.commit()
                return flask.redirect("?", code=303)


@app.route("/<username>/<repository>/")
def repositoryIndex(username, repository):
    return flask.redirect("./tree", code=302)


@app.route("/info/<username>/avatar")
def userAvatar(username):
    serverUserdataLocation = os.path.join(config.USERDATA_PATH, username)

    if not os.path.exists(serverUserdataLocation):
        return flask.render_template("not-found.html"), 404

    return flask.send_from_directory(serverUserdataLocation, "avatar.png")


@app.route("/<username>/<repository>/raw/<branch>/<path:subpath>")
def repositoryRaw(username, repository, branch, subpath):
    if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
                                                                      repository) is not None):
        flask.abort(403)

    serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)

    app.logger.info(f"Loading {serverRepoLocation}")

    if not os.path.exists(serverRepoLocation):
        app.logger.error(f"Cannot load {serverRepoLocation}")
        return flask.render_template("not-found.html"), 404

    repo = git.Repo(serverRepoLocation)
    repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
    if not repoData.defaultBranch:
        if repo.heads:
            repoData.defaultBranch = repo.heads[0].name
        else:
            return flask.render_template("empty.html",
                                         remote=f"http://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
    if not branch:
        branch = repoData.defaultBranch
        return flask.redirect(f"./{branch}", code=302)

    if branch.startswith("tag:"):
        ref = f"tags/{branch[4:]}"
    elif branch.startswith("~"):
        ref = branch[1:]
    else:
        ref = f"heads/{branch}"

    ref = ref.replace("~", "/")  # encode slashes for URL support

    try:
        repo.git.checkout("-f", ref)
    except git.exc.GitCommandError:
        return flask.render_template("not-found.html"), 404

    return flask.send_from_directory(config.REPOS_PATH, os.path.join(username, repository, subpath))


@repositories.route("/<username>/<repository>/tree/", defaults={"branch": None, "subpath": ""})
@repositories.route("/<username>/<repository>/tree/<branch>/", defaults={"subpath": ""})
@repositories.route("/<username>/<repository>/tree/<branch>/<path:subpath>")
def repositoryTree(username, repository, branch, subpath):
    if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
                                                                      repository) is not None):
        flask.abort(403)

    serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)

    app.logger.info(f"Loading {serverRepoLocation}")

    if not os.path.exists(serverRepoLocation):
        app.logger.error(f"Cannot load {serverRepoLocation}")
        return flask.render_template("not-found.html"), 404

    repo = git.Repo(serverRepoLocation)
    repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
    if not repoData.defaultBranch:
        if repo.heads:
            repoData.defaultBranch = repo.heads[0].name
        else:
            return flask.render_template("empty.html",
                                         remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
    if not branch:
        branch = repoData.defaultBranch
        return flask.redirect(f"./{branch}", code=302)

    if branch.startswith("tag:"):
        ref = f"tags/{branch[4:]}"
    elif branch.startswith("~"):
        ref = branch[1:]
    else:
        ref = f"heads/{branch}"

    ref = ref.replace("~", "/")  # encode slashes for URL support

    try:
        repo.git.checkout("-f", ref)
    except git.exc.GitCommandError:
        return flask.render_template("not-found.html"), 404

    branches = repo.heads

    allRefs = []
    for ref in repo.heads:
        allRefs.append((ref, "head"))
    for ref in repo.tags:
        allRefs.append((ref, "tag"))

    if os.path.isdir(os.path.join(serverRepoLocation, subpath)):
        files = []
        blobs = []

        for entry in os.listdir(os.path.join(serverRepoLocation, subpath)):
            if not os.path.basename(entry) == ".git":
                files.append(os.path.join(subpath, entry))

        infos = []

        for file in files:
            path = os.path.join(serverRepoLocation, file)
            mimetype = guessMIME(path)

            text = gitCommand(serverRepoLocation, None, "log", "--format='%H\n'", file).decode()

            sha = text.split("\n")[0]
            identifier = f"/{username}/{repository}/{sha}"
            lastCommit = Commit.query.filter_by(identifier=identifier).first()

            info = {
                "name": os.path.basename(file),
                "serverPath": path,
                "relativePath": file,
                "link": os.path.join(f"/{username}/{repository}/tree/{branch}/", file),
                "size": humanSize(os.path.getsize(path)),
                "mimetype": f"{mimetype}{f' ({mimetypes.guess_type(path)[1]})' if mimetypes.guess_type(path)[1] else ''}",
                "commit": lastCommit,
                "shaSize": 7,
            }

            specialIcon = config.matchIcon(os.path.basename(file))
            if specialIcon:
                info["icon"] = specialIcon
            elif os.path.isdir(path):
                info["icon"] = config.folderIcon
            elif mimetypes.guess_type(path)[0] in config.fileIcons:
                info["icon"] = config.fileIcons[mimetypes.guess_type(path)[0]]
            else:
                info["icon"] = config.unknownIcon

            if os.path.isdir(path):
                infos.insert(0, info)
            else:
                infos.append(info)

        return flask.render_template(
                "repo-tree.html",
                username=username,
                repository=repository,
                files=infos,
                subpath=os.path.join("/", subpath),
                branches=allRefs,
                current=branch,
                remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
                isFavourite=getFavourite(flask.session.get("username"), username, repository)
        )
    else:
        path = os.path.join(serverRepoLocation, subpath)

        if not os.path.exists(path):
            return flask.render_template("not-found.html"), 404

        mimetype = guessMIME(path)
        mode = mimetype.split("/", 1)[0]
        size = humanSize(os.path.getsize(path))

        specialIcon = config.matchIcon(os.path.basename(path))
        if specialIcon:
            icon = specialIcon
        elif os.path.isdir(path):
            icon = config.folderIcon
        elif mimetypes.guess_type(path)[0] in config.fileIcons:
            icon = config.fileIcons[mimetypes.guess_type(path)[0]]
        else:
            icon = config.unknownIcon

        contents = None
        if mode == "text":
            contents = convertToHTML(path)

        return flask.render_template(
                "repo-file.html",
                username=username,
                repository=repository,
                file=os.path.join(f"/{username}/{repository}/raw/{branch}/", subpath),
                branches=allRefs,
                current=branch,
                mode=mode,
                mimetype=mimetype,
                detailedtype=magic.from_file(path),
                size=size,
                icon=icon,
                subpath=os.path.join("/", subpath),
                basename=os.path.basename(path),
                contents=contents,
                remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
                isFavourite=getFavourite(flask.session.get("username"), username, repository)
        )


@repositories.route("/<username>/<repository>/forum/")
def repositoryForum(username, repository):
    if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
                                                                      repository) is not None):
        flask.abort(403)

    serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)

    app.logger.info(f"Loading {serverRepoLocation}")

    if not os.path.exists(serverRepoLocation):
        app.logger.error(f"Cannot load {serverRepoLocation}")
        return flask.render_template("not-found.html"), 404

    repo = git.Repo(serverRepoLocation)
    repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
    user = User.query.filter_by(username=flask.session.get("username")).first()
    relationships = RepoAccess.query.filter_by(repo=repoData)
    userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()

    return flask.render_template(
            "repo-forum.html",
            username=username,
            repository=repository,
            repoData=repoData,
            relationships=relationships,
            repo=repo,
            userRelationship=userRelationship,
            Post=Post,
            remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
            isFavourite=getFavourite(flask.session.get("username"), username, repository)
    )


@repositories.route("/<username>/<repository>/forum/new", methods=["POST"])
def repositoryForumAdd(username, repository):
    if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
                                                                      repository) is not None):
        flask.abort(403)

    serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)

    app.logger.info(f"Loading {serverRepoLocation}")

    if not os.path.exists(serverRepoLocation):
        app.logger.error(f"Cannot load {serverRepoLocation}")
        return flask.render_template("not-found.html"), 404

    repo = git.Repo(serverRepoLocation)
    repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
    user = User.query.filter_by(username=flask.session.get("username")).first()
    relationships = RepoAccess.query.filter_by(repo=repoData)
    userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()

    post = Post(user, repoData, None, flask.request.form["subject"], flask.request.form["message"])

    db.session.add(post)
    db.session.commit()

    return flask.redirect(flask.url_for(".repositoryForumThread", username=username, repository=repository, postID=post.number), code=303)


@repositories.route("/<username>/<repository>/forum/<int:postID>")
def repositoryForumThread(username, repository, postID):
    if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
                                                                      repository) is not None):
        flask.abort(403)

    serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)

    app.logger.info(f"Loading {serverRepoLocation}")

    if not os.path.exists(serverRepoLocation):
        app.logger.error(f"Cannot load {serverRepoLocation}")
        return flask.render_template("not-found.html"), 404

    repo = git.Repo(serverRepoLocation)
    repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
    user = User.query.filter_by(username=flask.session.get("username")).first()
    relationships = RepoAccess.query.filter_by(repo=repoData)
    userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()

    return flask.render_template(
            "repo-forum-thread.html",
            username=username,
            repository=repository,
            repoData=repoData,
            relationships=relationships,
            repo=repo,
            userRelationship=userRelationship,
            Post=Post,
            postID=postID,
            maxPostNesting=4,
            remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
            isFavourite=getFavourite(flask.session.get("username"), username, repository)
    )


@repositories.route("/<username>/<repository>/forum/<int:postID>/reply", methods=["POST"])
def repositoryForumReply(username, repository, postID):
    if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
                                                                      repository) is not None):
        flask.abort(403)

    serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)

    app.logger.info(f"Loading {serverRepoLocation}")

    if not os.path.exists(serverRepoLocation):
        app.logger.error(f"Cannot load {serverRepoLocation}")
        return flask.render_template("not-found.html"), 404

    repo = git.Repo(serverRepoLocation)
    repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
    user = User.query.filter_by(username=flask.session.get("username")).first()
    relationships = RepoAccess.query.filter_by(repo=repoData)
    userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
    if not user:
        flask.abort(401)

    parent = Post.query.filter_by(identifier=f"/{username}/{repository}/{postID}").first()
    post = Post(user, repoData, parent, flask.request.form["subject"], flask.request.form["message"])

    db.session.add(post)
    post.updateDate()
    db.session.commit()

    return flask.redirect(flask.url_for(".repositoryForumThread", username=username, repository=repository, postID=postID), code=303)


@app.route("/<username>/<repository>/forum/<int:postID>/voteup", defaults={"score": 1})
@app.route("/<username>/<repository>/forum/<int:postID>/votedown", defaults={"score": -1})
@app.route("/<username>/<repository>/forum/<int:postID>/votes", defaults={"score": 0})
def repositoryForumVote(username, repository, postID, score):
    if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
                                                                      repository) is not None):
        flask.abort(403)

    serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)

    app.logger.info(f"Loading {serverRepoLocation}")

    if not os.path.exists(serverRepoLocation):
        app.logger.error(f"Cannot load {serverRepoLocation}")
        return flask.render_template("not-found.html"), 404

    repo = git.Repo(serverRepoLocation)
    repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
    user = User.query.filter_by(username=flask.session.get("username")).first()
    relationships = RepoAccess.query.filter_by(repo=repoData)
    userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
    if not user:
        flask.abort(401)

    post = Post.query.filter_by(identifier=f"/{username}/{repository}/{postID}").first()

    if score:
        oldRelationship = PostVote.query.filter_by(userUsername=user.username, postIdentifier=post.identifier).first()
        if oldRelationship:
            if score == oldRelationship.voteScore:
                db.session.delete(oldRelationship)
                post.voteSum -= oldRelationship.voteScore
            else:
                post.voteSum -= oldRelationship.voteScore
                post.voteSum += score
                oldRelationship.voteScore = score
        else:
            relationship = PostVote(user, post, score)
            post.voteSum += score
            db.session.add(relationship)

        db.session.commit()

    userVote = PostVote.query.filter_by(userUsername=user.username, postIdentifier=post.identifier).first()
    response = flask.make_response(str(post.voteSum) + " " + str(userVote.voteScore if userVote else 0))
    response.content_type = "text/plain"

    return response


@app.route("/<username>/<repository>/favourite")
def repositoryFavourite(username, repository):
    if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
                                                                      repository) is not None):
        flask.abort(403)

    serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)

    app.logger.info(f"Loading {serverRepoLocation}")

    if not os.path.exists(serverRepoLocation):
        app.logger.error(f"Cannot load {serverRepoLocation}")
        return flask.render_template("not-found.html"), 404

    repo = git.Repo(serverRepoLocation)
    repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
    user = User.query.filter_by(username=flask.session.get("username")).first()
    relationships = RepoAccess.query.filter_by(repo=repoData)
    userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()
    if not user:
        flask.abort(401)

    oldRelationship = RepoFavourite.query.filter_by(userUsername=user.username, repoRoute=repoData.route).first()
    if oldRelationship:
        db.session.delete(oldRelationship)
    else:
        relationship = RepoFavourite(user, repoData)
        db.session.add(relationship)

    db.session.commit()

    return flask.redirect(flask.url_for("favourites"), code=303)


@repositories.route("/<username>/<repository>/users/", methods=["GET", "POST"])
def repositoryUsers(username, repository):
    if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
                                                                      repository) is not None):
        flask.abort(403)

    serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)

    app.logger.info(f"Loading {serverRepoLocation}")

    if not os.path.exists(serverRepoLocation):
        app.logger.error(f"Cannot load {serverRepoLocation}")
        return flask.render_template("not-found.html"), 404

    repo = git.Repo(serverRepoLocation)
    repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
    user = User.query.filter_by(username=flask.session.get("username")).first()
    relationships = RepoAccess.query.filter_by(repo=repoData)
    userRelationship = RepoAccess.query.filter_by(repo=repoData, user=user).first()

    if flask.request.method == "GET":
        return flask.render_template(
                "repo-users.html",
                username=username,
                repository=repository,
                repoData=repoData,
                relationships=relationships,
                repo=repo,
                userRelationship=userRelationship,
                remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
                isFavourite=getFavourite(flask.session.get("username"), username, repository)
        )
    else:
        if getPermissionLevel(flask.session.get("username"), username, repository) != 2:
            flask.abort(401)

        if flask.request.form.get("new-username"):
            # Create new relationship
            newUser = User.query.filter_by(username=flask.request.form.get("new-username")).first()
            relationship = RepoAccess(newUser, repoData, flask.request.form.get("new-level"))
            db.session.add(relationship)
            db.session.commit()
        if flask.request.form.get("update-username"):
            # Create new relationship
            updatedUser = User.query.filter_by(username=flask.request.form.get("update-username")).first()
            relationship = RepoAccess.query.filter_by(repo=repoData, user=updatedUser).first()
            if flask.request.form.get("update-level") == -1:
                relationship.delete()
            else:
                relationship.accessLevel = flask.request.form.get("update-level")
            db.session.commit()

        return flask.redirect(app.url_for(".repositoryUsers", username=username, repository=repository))


@repositories.route("/<username>/<repository>/branches/")
def repositoryBranches(username, repository):
    if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
                                                                      repository) is not None):
        flask.abort(403)

    serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)

    app.logger.info(f"Loading {serverRepoLocation}")

    if not os.path.exists(serverRepoLocation):
        app.logger.error(f"Cannot load {serverRepoLocation}")
        return flask.render_template("not-found.html"), 404

    repo = git.Repo(serverRepoLocation)
    repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()

    return flask.render_template(
            "repo-branches.html",
            username=username,
            repository=repository,
            repoData=repoData,
            repo=repo,
            remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
            isFavourite=getFavourite(flask.session.get("username"), username, repository)
    )


@repositories.route("/<username>/<repository>/log/", defaults={"branch": None})
@repositories.route("/<username>/<repository>/log/<branch>/")
def repositoryLog(username, repository, branch):
    if not (getVisibility(username, repository) or getPermissionLevel(flask.session.get("username"), username,
                                                                      repository) is not None):
        flask.abort(403)

    serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)

    app.logger.info(f"Loading {serverRepoLocation}")

    if not os.path.exists(serverRepoLocation):
        app.logger.error(f"Cannot load {serverRepoLocation}")
        return flask.render_template("not-found.html"), 404

    repo = git.Repo(serverRepoLocation)
    repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()
    if not repoData.defaultBranch:
        if repo.heads:
            repoData.defaultBranch = repo.heads[0].name
        else:
            return flask.render_template("empty.html",
                                         remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}"), 200
    if not branch:
        branch = repoData.defaultBranch
        return flask.redirect(f"./{branch}", code=302)

    if branch.startswith("tag:"):
        ref = f"tags/{branch[4:]}"
    elif branch.startswith("~"):
        ref = branch[1:]
    else:
        ref = f"heads/{branch}"

    ref = ref.replace("~", "/")  # encode slashes for URL support

    try:
        repo.git.checkout("-f", ref)
    except git.exc.GitCommandError:
        return flask.render_template("not-found.html"), 404

    branches = repo.heads

    allRefs = []
    for ref in repo.heads:
        allRefs.append((ref, "head"))
    for ref in repo.tags:
        allRefs.append((ref, "tag"))

    commitList = [f"/{username}/{repository}/{sha}" for sha in gitCommand(serverRepoLocation, None, "log", "--format='%H'").decode().split("\n")]

    commits = Commit.query.filter(Commit.identifier.in_(commitList))

    return flask.render_template(
            "repo-log.html",
            username=username,
            repository=repository,
            branches=allRefs,
            current=branch,
            repoData=repoData,
            repo=repo,
            commits=commits,
            remote=f"http{'s' if config.suggestHTTPS else ''}://{config.BASE_DOMAIN}/git/{username}/{repository}",
            isFavourite=getFavourite(flask.session.get("username"), username, repository)
    )


@repositories.route("/<username>/<repository>/settings/")
def repositorySettings(username, repository):
    if getPermissionLevel(flask.session.get("username"), username, repository) != 2:
        flask.abort(401)

    return flask.render_template("repo-settings.html", username=username, repository=repository)


@app.errorhandler(404)
def e404(error):
    return flask.render_template("not-found.html"), 404


@app.errorhandler(401)
def e401(error):
    return flask.render_template("unauthorised.html"), 401


@app.errorhandler(403)
def e403(error):
    return flask.render_template("forbidden.html"), 403


@app.errorhandler(418)
def e418(error):
    return flask.render_template("teapot.html"), 418


@app.errorhandler(405)
def e405(error):
    return flask.render_template("method-not-allowed.html"), 405


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


app.register_blueprint(repositories)
