#!/usr/bin/env python

from functools import wraps
import logging
import getopt
import sys
import json
import shlex
import re

from requests import Session

class ApiError(Exception):
    pass

def status_ok(f):
    @wraps(f)
    def wrapper(self, *args, **kwds):
        r = f(self, *args, **kwds)
        if r.status_code != 200:
            raise ApiError(str(r.status_code))
        return r
    return wrapper


def result_ok(f):
    @wraps(f)
    def wrapper(self, *args, **kwds):
        r = f(self, *args, **kwds)
        j = r.json()
        if 'result' in j:
            return j['result']
        if 'error' in j:
            logging.error('API ERROR %s', j['error'])
            raise ApiError(j['error'])
        return None
    return wrapper


class Api():
    def __init__(self, api_root):
        self.root = api_root
        self.api_root = api_root
        self.session = Session()
        self.result = None

    def http(self, f, url, data=None, json=None, params=None, files=None):
        if f == 'post':
            self.result = self.session.post(url, verify=False, data=data, json=json, files=files)
        elif f == 'put':
            self.result = self.session.put(url, verify=False, data=data, json=json)
        elif f == 'get':
            self.result = self.session.get(url, verify=False, params=params)
        elif f == 'delete':
            self.result = self.session.delete(url, verify=False, params=params)
        else:
            return None
        logging.debug("request  headers: %s", self.result.request.headers)
        logging.debug("response headers: %s", self.result.headers)
        return self.result

    @status_ok
    def api_call(self, f, path, json=None, data=None, params=None, files=None):
        return self.http(f, self.api_root + path, data, json, params, files)

    def login(self, username, password):
        self.http('post', self.api_root + '/api/login', json={'user': username, 'password': password})

    def logout(self):
        self.http('post', self.api_root + '/api/logout')

if __name__ == "__main__":
    show_headers = False
    api = None
    def run(args):
        cmd = args[0].lower()
        route = args[1]
        try:
            if cmd == 'upload':
                if len(args) > 3:
                    api.api_call('post', route, files={args[2]:open(args[3])})
            else:
                if len(args) > 2:
                    params = json.loads(args[2])
                else:
                    params = None
                if cmd in ['get', 'delete']:
                    api.api_call(cmd, route, params=params)
                elif cmd in ['put', 'post']:
                    api.api_call(cmd, route, json=params)

                print ()
                print ("request:")
                print (cmd, api.api_root + api.result.request.path_url)
                if params:
                    print ()
                    print (json.dumps(params, indent=4, sort_keys=True))

            if show_headers:
                print ("\nrequest  headers:")
                print (api.result.request.headers)
                print ("\nresponse headers:")
                print (api.result.headers)

            print ("\nresponse:")
            # print (api.result.text)
            print (json.dumps(api.result.json(), indent=4, sort_keys=True))

        except Exception as e:
            print ("Failed call", str(e))

    try:
        opts, args = getopt.getopt(sys.argv[1:],
                                   "l:o:h:u:p:Hi:",
                                   ["loglevel=", "output=", "host=", "user=", "pass=", "input="])
    except getopt.GetoptError as err:
        # print help information and exit:
        print(str(err))
        sys.exit(2)

    loglevel = logging.ERROR
    output = None
    infile = None
    user = 'admin'
    passwd = 'admin'
    root = None
    for o, a in opts:
        if o in ("-l", "--loglevel"):
            if a == 'ERROR':
                loglevel = logging.ERROR
            elif a == 'WARNING':
                loglevel = logging.WARNING
            elif a == 'DEBUG':
                loglevel = logging.DEBUG
        elif o in ("-o", "--output"):
            output = a
        elif o in ("-i", "--infile"):
            infile = a
        elif o in ("-h", "--host"):
            if a.startswith('http://'):
                root = a
            elif a.startswith('https://'):
                root = a
            else:
                root = 'http://' + a
        elif o in ("-u", "--user"):
            user = a
        elif o in ("-p", "--pass"):
            passwd = a
        elif o in ("-H",):
            show_headers = True
        else:
            assert False, "unhandled option"

    logging.basicConfig(level=loglevel,
                        filename=output,
                        filemode='w')

    if root is None:
        print("spotbox_api -h hostip cmd api_route args")

    api = Api(root)

    if infile is not None:
        print (infile)
        api.login(user, passwd)
        prepend = ""
        with open(infile) as inf:
            for l in inf:
                l = re.sub('[\r\n]*', '', l)
                if l.endswith("\\"):
                    prepend += ' ' + l[:-1]
                else:
                    l = re.sub('#.*', '', prepend + l).strip().rstrip()
                    if len(l):
                        print(l)
                        run(shlex.split(l))
                    prepend = ""
        api.logout()
    elif len(args) >= 2:
        api.login(user, passwd)
        run(args)
        api.logout()
    else:
        print ("spotbox_api <-u username> <-p password> -h hostip cmd api_route args")
        print ("spotbox_api <-u username> <-p password> -h hostip -i filename")
        print ("eg: spotbox_api -h 192.168.0.222 get /api/view/mode")

    logging.shutdown()
