# -*- coding: utf-8 -*-
# Copyright 2014 matrix.org
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.


import ConfigParser as configparser
import argparse
import socket
import sys
import os
from OpenSSL import crypto
import nacl.signing
from syutil.base64util import encode_base64
import subprocess


def load_config(description, argv):
    config_parser = argparse.ArgumentParser(add_help=False)
    config_parser.add_argument("-c", "--config-path", metavar="CONFIG_FILE",
                               help="Specify config file")
    config_args, remaining_args = config_parser.parse_known_args(argv)
    if config_args.config_path:
        config = configparser.SafeConfigParser()
        config.read([config_args.config_path])
        defaults = dict(config.items("KeyServer"))
    else:
        defaults = {}
    parser = argparse.ArgumentParser(
        parents=[config_parser],
        description=description,
        formatter_class=argparse.RawDescriptionHelpFormatter,
    )
    parser.set_defaults(**defaults)
    parser.add_argument("--server-name", default=socket.getfqdn(),
                        help="The name of the server")
    parser.add_argument("--signing-key-path",
                        help="The signing key to sign responses with")
    parser.add_argument("--tls-certificate-path",
                        help="PEM encoded X509 certificate for TLS")
    parser.add_argument("--tls-private-key-path",
                        help="PEM encoded private key for TLS")
    parser.add_argument("--tls-dh-params-path",
                        help="PEM encoded dh parameters for ephemeral keys")
    parser.add_argument("--bind-port", type=int,
                        help="TCP port to listen on")
    parser.add_argument("--bind-host", default="",
                        help="Local interface to listen on")

    args = parser.parse_args(remaining_args)

    server_config = vars(args)
    del server_config["config_path"]
    return server_config


def generate_config(argv):
    parser = argparse.ArgumentParser()
    parser.add_argument("-c", "--config-path", help="Specify config file",
                        metavar="CONFIG_FILE", required=True)
    parser.add_argument("--server-name", default=socket.getfqdn(),
                        help="The name of the server")
    parser.add_argument("--signing-key-path",
                        help="The signing key to sign responses with")
    parser.add_argument("--tls-certificate-path",
                        help="PEM encoded X509 certificate for TLS")
    parser.add_argument("--tls-private-key-path",
                        help="PEM encoded private key for TLS")
    parser.add_argument("--tls-dh-params-path",
                        help="PEM encoded dh parameters for ephemeral keys")
    parser.add_argument("--bind-port", type=int, required=True,
                        help="TCP port to listen on")
    parser.add_argument("--bind-host", default="",
                        help="Local interface to listen on")

    args = parser.parse_args(argv)

    dir_name = os.path.dirname(args.config_path)
    base_key_name = os.path.join(dir_name, args.server_name)

    if args.signing_key_path is None:
        args.signing_key_path = base_key_name + ".signing.key"

    if args.tls_certificate_path is None:
        args.tls_certificate_path = base_key_name + ".tls.crt"

    if args.tls_private_key_path is None:
        args.tls_private_key_path = base_key_name + ".tls.key"

    if args.tls_dh_params_path is None:
        args.tls_dh_params_path = base_key_name + ".tls.dh"

    if not os.path.exists(args.signing_key_path):
        with open(args.signing_key_path, "w") as signing_key_file:
            key = nacl.signing.SigningKey.generate()
            signing_key_file.write(encode_base64(key.encode()))

    if not os.path.exists(args.tls_private_key_path):
        with open(args.tls_private_key_path, "w") as private_key_file:
            tls_private_key = crypto.PKey()
            tls_private_key.generate_key(crypto.TYPE_RSA, 2048)
            private_key_pem = crypto.dump_privatekey(
                crypto.FILETYPE_PEM, tls_private_key
            )
            private_key_file.write(private_key_pem)
    else:
        with open(args.tls_private_key_path) as private_key_file:
            private_key_pem = private_key_file.read()
            tls_private_key = crypto.load_privatekey(
                crypto.FILETYPE_PEM, private_key_pem
            )

    if not os.path.exists(args.tls_certificate_path):
        with open(args.tls_certificate_path, "w") as certifcate_file:
            cert = crypto.X509()
            subject = cert.get_subject()
            subject.CN = args.server_name

            cert.set_serial_number(1000)
            cert.gmtime_adj_notBefore(0)
            cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60)
            cert.set_issuer(cert.get_subject())
            cert.set_pubkey(tls_private_key)

            cert.sign(tls_private_key, 'sha256')

            cert_pem = crypto.dump_certificate(crypto.FILETYPE_PEM, cert)

            certifcate_file.write(cert_pem)

    if not os.path.exists(args.tls_dh_params_path):
        subprocess.check_call([
            "openssl", "dhparam",
            "-outform", "PEM",
            "-out", args.tls_dh_params_path,
            "2048"
        ])

    config = configparser.SafeConfigParser()
    config.add_section("KeyServer")
    for key, value in vars(args).items():
        if key != "config_path":
            config.set("KeyServer", key, str(value))

    with open(args.config_path, "w") as config_file:
        config.write(config_file)


if __name__ == "__main__":
    generate_config(sys.argv[1:])