#!/usr/bin/env python3

''' API client for bocal-site '''

import json
import urllib.request
import hmac
import hashlib
from datetime import datetime, date
import argparse
import os.path
import sys


def sendReq(url):
    def send(payload, host):
        try:
            req = urllib.request.Request('https://{}/{}'.format(host, url),
                                         json.dumps(payload).encode('ascii'))
            req.add_header('Content-Type', 'application/json')
            handle = urllib.request.urlopen(req)
            code = handle.getcode()
            content = handle.read()
            handle.close()
            return (code, content.decode('utf-8'))
        except urllib.error.HTTPError as e:
            return (e.code, e.read().decode('utf-8'))

    def authentify(apiKey, payload):
        keyId, key = apiKey.split('$')
        keyId = int(keyId)
        time = datetime.now().timestamp()
        mac = hmac.new(key.encode('utf-8'),
                       msg=str(int(time)).encode('utf-8'),
                       digestmod=hashlib.sha256)
        payload_enc = json.dumps(payload)
        mac.update(payload_enc.encode('utf-8'))

        auth = {
            'keyId': keyId,
            'timestamp': time,
            'hmac': mac.hexdigest(),
        }

        return {
            'auth': auth,
            'req': payload_enc,
        }

    def decorator(fct):
        ''' Decorator. Adds authentication layer. '''
        def wrap(host, apiKey, *args, **kwargs):
            innerReq = fct(*args, **kwargs)
            payload = authentify(apiKey, innerReq)
            return send(payload, host)
        return wrap

    return decorator


@sendReq(url='api/publish')
def publish(bocId, url, date):
    return {
        'id': bocId,
        'url': url,
        'date': date.strftime('%Y-%m-%d'),
    }


###############################################################################

TOKEN_DFT_FILE = os.path.expanduser("~/.bocal_api_token")
DFT_HOST = 'bocal.cof.ens.fr'


def read_token(path):
    token = ''
    try:
        with open(path, 'r') as handle:
            token = handle.readline().strip()
    except FileNotFoundError:
        print("[Erreur] Fichier d'identifiants absent (`{}`).".format(path),
              file=sys.stderr)
        sys.exit(1)
    return token


def cmd(func):
    def wrap(parse_args, *args, **kwargs):
        token = read_token(parse_args.creds)
        return func(token, parse_args, *args, **kwargs)
    return wrap


@cmd
def cmd_publish(token, args):
    if not args.date:
        publish_date = date.today()
    else:
        year, month, day = [int(x) for x in args.date.strip().split('-')]
        publish_date = date(year=year, month=month, day=day)
    (ret_code, ret_str) = publish(args.host,
                                  token,
                                  args.numero,
                                  args.url,
                                  publish_date)
    if ret_code == 200:
        print("Succès :)")
    else:
        print("[Erreur :c] {} : {}".format(ret_code, ret_str))
        sys.exit(1)


def setup_argparse():
    parser = argparse.ArgumentParser()
    parser.add_argument('--host',
                        help=("Adresse du site à contacter (par défaut, "
                              "`{}`).".format(DFT_HOST)))
    parser.add_argument('--creds',
                        help=("Fichier contenant le token API à utiliser "
                              "(par défaut, `{}`)".format(TOKEN_DFT_FILE)))
    parser.set_defaults(creds=TOKEN_DFT_FILE, host=DFT_HOST)
    subparsers = parser.add_subparsers()

    parser_publish = subparsers.add_parser('publier',
                                           help='Publier un numéro du BOcal')
    parser_publish.add_argument('numero',
                                help='Numéro du BOcal')
    parser_publish.add_argument('url',
                                help='Adresse (locale) du PDF du BOcal')
    parser_publish.add_argument('-d', '--date',
                                help="Date de publication indiquée")
    parser_publish.set_defaults(func=cmd_publish)

    out_args = parser.parse_args()
    if 'func' not in out_args:  # No subcommand provided
        print("You must provide a command.", file=sys.stderr)
        print(parser.parse_args(['-h']), file=sys.stderr)
        sys.exit(1)
    return out_args


def main():
    args = setup_argparse()
    args.func(args)


if __name__ == '__main__':
    main()