import uuid

from gitme import app, User, Repo, db, bcrypt
import os
import shutil
import config
import flask
import git
import subprocess
from flask_httpauth import HTTPBasicAuth

auth = HTTPBasicAuth()


@auth.verify_password
def verifyPassword(username, 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.g.user = username
        return True

    return False


@app.route("/git/<username>/<repository>/<path:subpath>", methods=["PROPFIND"])
def gitList(username, repository, subpath):
    serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository, ".git", subpath)

    if not os.path.exists(serverRepoLocation):
        flask.abort(404)

    text = """<?xml version="1.0" encoding="utf-8" ?>
    <D:multistatus xmlns:D="DAV:">
    """

    for file in os.listdir(serverRepoLocation):
        text += f"""
        <D:response>
            <D:href>/git/{username}/{repository}/</D:href>
        """

        if os.path.isdir(file):
            text += """
            <D:propstat>
                <D:prop>
                    <D:resourcetype>
                        <D:collection/>
                    </D:resourcetype>
                </D:prop>
                <D:status>HTTP/1.1 200 OK</D:status>
            </D:propstat>
            """
        else:
            text += """
            <D:propstat>
                <D:prop/>
                <D:status>HTTP/1.1 200 OK</D:status>
            </D:propstat>
            """

        text += """
        </D:response>"""

    text += """
    </D:multistatus>"""

    return text


@app.route("/git/<username>/<repository>/", methods=["GET", "POST"])
def gitRoot(username, repository):
    serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
    app.logger.info(f"Loading {serverRepoLocation}")

    if not os.path.exists(serverRepoLocation):
        app.logger.error(f"Cannot load {serverRepoLocation}")
        flask.abort(404)

    repo = git.Repo(serverRepoLocation)


@app.route("/git/<username>/<repository>/<path:git_path>", methods=["GET", "POST"])
@auth.login_required
def gitInfo(username, repository, git_path=""):
    serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
    app.logger.info(f"Loading {serverRepoLocation}")

    if not os.path.exists(serverRepoLocation):
        app.logger.error(f"Cannot load {serverRepoLocation}")
        flask.abort(404)

    repo = git.Repo(serverRepoLocation)

    if flask.request.method == "GET":
        method = "upload-pack"
        result = repo.git.upload_pack()
    elif flask.request.method == "POST":
        method = "receive-pack"
        if flask.request.headers.get("Content-Type") == "application/x-git-receive-pack-request":
            result = repo.git.receive_pack()
        else:
            result = "Not supported"

    response = flask.Response(result, content_type=f"application/x-git-{method}-result")
    return response


@app.route("/git/<username>/<repository>/<path:git_path>", methods=["MKCOL"])
def gitDummyMkCol(username, repository, git_path):
    return "", 200


@app.route("/git/<username>/<repository>/info/", methods=["MKCOL"])
def gitMakeInfo(username, repository):
    serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
    app.logger.info(f"Loading {serverRepoLocation}")

    if not os.path.exists(serverRepoLocation):
        app.logger.error(f"Cannot load {serverRepoLocation}")
        flask.abort(404)

    if not os.path.exists(os.path.join(serverRepoLocation, ".git", "info")):
        os.makedirs(os.path.join(serverRepoLocation, ".git", "info"))

    return "", 200


@app.route("/git/<username>/<repository>/info/refs")
def gitInfoRefs(username, repository):
    serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
    app.logger.info(f"Loading {serverRepoLocation}")
    repoData = Repo.query.filter_by(route=f"/{username}/{repository}").first()

    if not os.path.exists(serverRepoLocation):
        app.logger.error(f"Cannot load {serverRepoLocation}")
        flask.abort(404)

    text = ""
    repo = git.Repo(serverRepoLocation)
    for i in repo.heads:
        if i.name == repoData.defaultBranch:
            text = f"{i.commit.hexsha}\tHEAD\n"
            break

    for ref in repo.heads:
        text += f"{ref.commit.hexsha}\trefs/heads/{ref.name}\n"

    app.logger.warning(text)

    return flask.Response(text, content_type=f"application/x-{flask.request.args.get('service')}-result")


@app.route("/git/<username>/<repository>/objects/info/alternates", methods=["GET"])
@auth.login_required
def gitInfoAlternates(username, repository):
    text = "#Alternate object stores\n"
    return flask.Response(text, content_type="text/plain")


@app.route("/git/<username>/<repository>/objects/info/http-alternates")
@auth.login_required
def gitHttpAlternates(username, repository):
    return flask.Response("", content_type="text/plain")


@app.route("/git/<username>/<repository>/HEAD")
@auth.login_required
def gitHead(username, repository):
    serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
    app.logger.info(f"Loading {serverRepoLocation}")

    if not os.path.exists(serverRepoLocation):
        app.logger.error(f"Cannot load {serverRepoLocation}")
        flask.abort(404)

    repo = git.Repo(serverRepoLocation)
    text = f"ref: {repo.head.ref}\n"
    return flask.Response(text, content_type="text/plain")


@app.route("/git/<username>/<repository>/objects/info/packs")
def gitInfoPacks(username, repository):
    serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository, ".git")
    packs = [f for f in os.listdir(os.path.join(serverRepoLocation, "objects", "pack")) if f.endswith(".pack")]
    text = "\n".join(packs) + "\n"
    return flask.Response(text, content_type="text/plain")


@app.route("/git/<username>/<repository>/objects/<path:objectPath>")
@auth.login_required
def gitObject(username, repository, objectPath):
    serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
    objectFullPath = os.path.join(serverRepoLocation, ".git/objects", objectPath)

    if not os.path.exists(objectFullPath):
        return flask.Response("Object not found", status=404, content_type="text/plain")

    with open(objectFullPath, "rb") as f:
        objectData = f.read()

    return flask.Response(objectData, content_type="application/octet-stream")


@app.route("/git/<username>/<repository>/<path:itemPath>", methods=["PUT"])
# @auth.login_required
def gitPut(username, repository, itemPath):
    serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
    itemFullPath = os.path.join(serverRepoLocation, ".git", itemPath)

    if not os.path.exists(os.path.dirname(itemFullPath)):
        os.makedirs(os.path.dirname(itemFullPath))

    with open(itemFullPath, "wb") as f:
        f.write(flask.request.data)

    return "", 200


@app.route("/git/<username>/<repository>/<path:itemPath>", methods=["MOVE"])
# @auth.login_required
def gitMove(username, repository, itemPath):
    serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)
    itemFullPath = os.path.join(serverRepoLocation, ".git", itemPath)
    newPath = os.path.join(serverRepoLocation, ".git", flask.request.headers["destination"].split("/", 6)[-1])

    app.logger.info(f"move {itemPath} to {newPath}")

    shutil.move(itemFullPath, newPath)
    return "", 200


@app.route("/git/<username>/<repository>/git-upload-pack", methods=["POST"])
@auth.login_required
def gitUploadPack(username, repository):
    serverRepoLocation = os.path.join(config.REPOS_PATH, os.path.join(username, repository))
    cmd = ["git", "--git-dir", serverRepoLocation, "upload-pack"]

    gitProcess = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

    inputData = flask.request.get_data()
    output = gitProcess.communicate(input=inputData)
    return flask.Response(output[0], content_type="application/x-git-upload-pack-result")


@app.route("/git/<username>/<repository>/git-receive-pack", methods=["POST"])
@auth.login_required
def gitReceivePack(username, repository):
    serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)

    proc = subprocess.Popen(['git', '--work-tree', serverRepoLocation, 'receive-pack'], stdin=subprocess.PIPE,
                            stdout=subprocess.PIPE)
    proc.stdin.write(flask.request.get_data())
    proc.stdin.close()
    response = proc.stdout.read()

    return flask.Response(response, content_type='application/x-git-receive-pack-result')


@app.route("/git/<username>/<repository>/", methods=["PROPFIND"])
def gitPropFind(username, repository):
    serverRepoLocation = os.path.join(config.REPOS_PATH, username, repository)

    if not os.path.exists(serverRepoLocation):
        flask.abort(404)

    repo = git.Repo(serverRepoLocation)

    branches = ""
    for branch in repo.heads:
        branches += f"""
    <ns0:branch xmlns="DAV:">
        <ns0:name>{branch.name}</ns0:name>  
    </ns0:branch>
    """

    response = f"""<?xml version="1.0" encoding="UTF-8"?>
    <D:multistatus xmlns:D="DAV:">
        <D:response>
            <D:href>/git/{username}/{repository}/</D:href>  
            <D:propstat>
                <D:status>HTTP/1.1 200 OK</D:status>
                <D:prop>
                    <D:displayname>{repository}</D:displayname>
                    <D:resourcetype><D:collection/></D:resourcetype>
                    <D:supportedlock>
                        <D:lockentry>
                            <D:lockscope><D:exclusive/></D:lockscope>
                            <D:locktype><D:write/></D:locktype>
                        </D:lockentry>
                    </D:supportedlock>
                </D:prop>
            </D:propstat>
        </D:response>
    </D:multistatus>
    """

    return flask.Response(response, content_type="application/xml")


locks = {}


@app.route("/git/<username>/<repository>/<path:subpath>", methods=["LOCK", "UNLOCK"])
def gitLock(username, repository, subpath):
    if flask.request.method == "LOCK":
        scope = flask.request.args.get("lockscope")
        type_ = flask.request.args.get("locktype")

        if username + "/" + repository in locks:
            return "Lock already exists", 423

        lockToken = uuid.uuid4().hex
        if config.locking:
            locks[username + "/" + repository] = {
                "scope": scope,
                "type": type_,
                "token": lockToken
            }

        text = f"""<?xml version="1.0" encoding="UTF-8"?>
        <D:prop>
            <D:lockdiscovery>
                <D:activelock>
                    <D:lockscope><D:exclusive/></D:lockscope>
                    <D:locktype><D:write/></D:locktype>
                    <D:depth>Infinity</D:depth>
                    <D:owner>
                        <D:href>https://{config.BASE_DOMAIN}/{username}</D:href>
                    </D:owner>
                    <D:timeout>Second-600</D:timeout>
                    <D:locktoken>
                        <D:href>opaquelocktoken:{lockToken}</D:href>
                    </D:locktoken>
                </D:activelock>
            </D:lockdiscovery>
        </D:prop>
        """

        return text
    elif flask.request.method == "UNLOCK":
        lockToken = flask.request.headers.get("Lock-Token")

        if config.locking and locks[username + "/" + repository]["token"] == lockToken:
            del locks[username + "/" + repository]

        return ""
