#!/usr/bin/env python3
from http.server import BaseHTTPRequestHandler
from http.server import HTTPServer
from functools import partial
import os
import sys
import argparse
import configparser
import socket
import subprocess
import json

class HTTPServerV6(HTTPServer):
        address_family = socket.AF_INET6

class RequestHandler(BaseHTTPRequestHandler):

    def __init__(self, config, *args, **kwargs):
        self.config = config
        super().__init__(*args, **kwargs)

    def fatal(self, code, message):
        self.send_response(code)
        self.end_headers()
        self.wfile.write(message.encode())

    def do_POST(self):
        gitlab_hook_type = self.headers['X-Gitlab-Event']
        if gitlab_hook_type is None:
            self.send_response(400)
            self.end_headers()
            self.wfile.write("This is not a Gitlab webhook event")
            print("Request is not a Gitlab webhook. It doesn't contain the X-Gitlab-Event header.")
            return

        remote_addr = self.client_address[0]
        print("Received hook: {} from {}".format(gitlab_hook_type, remote_addr))

        length = int(self.headers['content-length'])
        message = json.loads(self.rfile.read(length))

        repository = message['project']['path_with_namespace']
        if repository == 'DEFAULT' or repository == 'general':
            return self.fatal(418, "Thats not a valid repository")

        if repository not in self.config:
            return self.fatal(404, "Repository config not found")

        config = self.config[repository]

        if 'ip' in config and remote_addr != config['ip']:
            print("Gitlab events not allowed from {}.".format(remote_addr))
            return self.fatal(403, "Gitlab events not allowed from this IP")

        if 'token' in config:
            token = self.headers['X-Gitlab-Token']
            if token != config['token']:
                return self.fatal(403, "Invalid token")

        cwd = None
        if 'cwd' in config:
            cwd = config['cwd']

        if gitlab_hook_type == "Push Hook":
            if 'exec-commit' not in config:
                print("No command defined for push events")
            else:
                subprocess.call(config['exec-commit'], shell=True, cwd=cwd)
        elif gitlab_hook_type == "Tag Push Hook":
            if 'exec-tag' not in config:
                print("No command defined for tag events")
            else:
                subprocess.call(config['exec-tag'], shell=True, cwd=cwd)

        self.send_response(200)
        self.end_headers()
        self.wfile.write(b'ok')


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description="Pully Gitlab webhook receiver")
    parser.add_argument('config', help="Path to the pully.conf file")
    args = parser.parse_args()

    if not os.path.isfile(args.config):
        sys.stderr.write(f"Config file '{args.config}' does not exist\n")
        exit(1)

    config = configparser.ConfigParser()
    config.read(args.config)

    host = '0.0.0.0'
    port = 1234
    if 'general' in config:
        if 'host' in config['general']:
            host = config.get('general', 'host')
        if 'port' in config['general']:
            port = int(config.get('general', 'port'))

    for section in config:
        if section == 'general' or section == 'DEFAULT':
            continue
        print(f"Accept pulls from {section}")

    handler = partial(RequestHandler, config)

    if ':' in host:
        server = HTTPServerV6((host, port), handler)
        print(f'Starting Gitlab receiver on [{host}]:{port}')
    else:
        server = HTTPServer((host, port), handler)
        print(f'Starting Gitlab receiver on {host}:{port}')

    server.serve_forever()

