#!/usr/bin/env python3
# coding: utf-8
# vi: tabstop=8 expandtab shiftwidth=4 softtabstop=4

import sys
import os
import argparse
import logging
import time
import socket
import datetime
import re
import json
import uuid
import urllib.parse
import urllib.request
import urllib.error
import functools as ft


# Configuration
log_facility = 1

# End of configuration


class RFC5424Formatter(logging.Formatter):
    PRI = [0, 7, 6, 4, 3, 2]

    def __init__(self, *args, **kwargs):
        self._tz_fix = re.compile(r'([+-]\d{2})(\d{2})$')
        try:
            self.hn = socket.gethostname()
        except Exception:
            self.hn = '-'
        super(RFC5424Formatter, self).__init__(*args, **kwargs)

    def format(self, record):
        record.__dict__['hostname'] = self.hn
        isotime = datetime.datetime.fromtimestamp(record.created).isoformat()
        tz = self._tz_fix.match(time.strftime('%z'))
        if time.timezone and tz:
            (offset_hrs, offset_min) = tz.groups()
            isotime = '{0}{1}:{2}'.format(isotime, offset_hrs, offset_min)
        else:
            isotime = isotime + 'Z'
        record.__dict__['isotime'] = isotime
        record.__dict__['pri'] = f"<{log_facility * 8 + self.PRI[int(record.levelno / 10)]}>1"
        return super(RFC5424Formatter, self).format(record)


class Response:
    def __init__(self, status_code, url, headers, content):
        self.status_code = status_code
        self.url = url
        self.headers = headers
        self.content = content
        self.ok = 200 <= status_code <= 299
        self.content_json = None

    def handle_error(self, step=None):
        if not self.ok:
            tx = self.headers.get('X-42c-Transactionid', '')
            res = [f"error on transaction {tx}"]
            if step is not None:
                res.append(f"at step '{step}'")
            res.append(f"with status code {self.status_code}")

            try:
                err = self.json()
            except Exception:
                res.append(f"message: {self.content}")
            else:
                code = err.get('code')
                msg = err.get('message')
                details = err.get('details', [])
                if msg:
                    res.append(f"message: {msg}")
                if logger.getEffectiveLevel() < logging.INFO:
                    if code:
                        res.append(f"code: {code}")
                    if len(details) > 0:
                        res.append(f"details: {details}")

            logger.critical(', '.join(res))
            sys.exit(1)

    def json(self):
        if self.content_json is None:
            self.content_json = json.loads(self.content)
        return self.content_json

    def __str__(self):
        return f"{self.url}: <{self.status_code}>, {self.headers}, {self.content}"


class requests:
    @staticmethod
    def request(method, url, headers=None, data=None):
        req = urllib.request.Request(url)
        req.method = method
        for k, v in headers.items():
            req.add_header(k, v)

        if isinstance(data, dict):
            req.data = json.dumps(data).encode('utf-8')
            if 'Content-Type' not in headers:
                req.add_header('Content-Type', 'application/json')
        else:
            req.data = data

        try:
            with urllib.request.urlopen(req) as f:
                res = Response(f.status, f.url, f.headers, f.read())
        except urllib.error.HTTPError as e:
            res = Response(e.code, e.filename, e.hdrs, e.fp.read())

        return res


for i in ["head", "get", "put", "post", "patch", "delete"]:
    setattr(requests, i, ft.partial(requests.request, i.upper()))


def gen_name():
    return str(uuid.uuid4())


def get_instance(instance_name):
    r = requests.get(url + '/api/v2/instances', headers=api_key)
    r.handle_error('get instances')
    instances = r.json()
    instance = None
    for i in instances['list']:
        if i['name'] == instance_name:
            instance = i

    if instance is None:
        logger.info(f"Creating instance {instance_name}")
        r = requests.post(url + '/api/v2/instances', headers=api_key,
                          data={'name': instance_name})
        r.handle_error('create instance')
        instance = r.json()
        r = requests.post(url + '/api/v2/protectionTokens', headers=api_key,
                          data={'name': instance_name, 'instanceId': instance['id']})
        r.handle_error('create protection token')
        tok = r.json()['value']
        print(f"CREATED PROTECTION TOKEN for instance {instance_name}: {tok}")
    else:
        logger.warning(f"Instance {instance_name} already exists")


def main(args):
    get_instance(args.instance_name)
    return 0


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('-v', '--verbose', required=False, action='count', default=0)
    parser.add_argument('-p', '--platform', required=False, default='https://platform.42crunch.com', type=str)
    parser.add_argument('--instance-name', required=True, type=str)
    parser.add_argument('--api-token', required=False, type=str)
    args = parser.parse_args()
    logger = logging.getLogger('42crunch-bootstrap')

    log_err = logging.StreamHandler(sys.stderr)
    formatter = RFC5424Formatter('%(pri)s %(isotime)s %(hostname)s %(name)s - - %(levelname)s %(message)s')
    log_err.setFormatter(formatter)
    logger.addHandler(log_err)
    logger.setLevel([logging.WARNING, logging.INFO, logging.DEBUG][min(args.verbose, 2)])

    if args.api_token is not None:
        api_key = {'X-API-KEY': args.api_token}
    else:
        try:
            api_key = {'X-API-KEY': os.environ['API_KEY']}
        except KeyError:
            logger.critical('API TOKEN not found')
            sys.exit(1)

    url = args.platform
    sys.exit(main(args))
