#!/usr/bin/python3

#
# Copyright (C) 2022 Nethesis S.r.l.
# http://www.nethesis.it - nethserver@nethesis.it
#
# This script is part of NethServer.
#
# NethServer is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License,
# or any later version.
#
# NethServer is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with NethServer.  If not, see COPYING.
#

import time
import sys
import subprocess
import argparse
import json
import hashlib
import uuid
from urllib import request, parse
from urllib.error import HTTPError

def run_task(api_endpoint, token, agent, action, data):
    global args

    data_extra = {
        "title": agent + "/" + action,
        "description": "ns8-action endpoint " + api_endpoint,
        "isNotificationHidden": True if args.hidden else False,
    }

    jdata = json.dumps({"action": action, "data": data, "extra": data_extra}).encode('utf8')
    req = request.Request(f'{api_endpoint}/api/{agent}/tasks', data=jdata)
    req.add_header('Content-Type', 'application/json')
    req.add_header('Authorization', f'Bearer {token}')
    post = request.urlopen(req)
    post_response = json.loads(post.read())

    # wait for the cluster queue to grab the request
    time.sleep(0.5)

    task_id = post_response["data"]["id"]

    return f"{agent}/task/{task_id}"

def wait_task(api_endpoint, token, task_id):
    """Wait for the running task_id, until its response is ready or a
    network/HTTP error occurs."""
    watchdog = 0
    while True:
        if watchdog > 9:
            watchdog = 0
            context_request = request.Request(f'{api_endpoint}/api/{task_id}/context')
            context_request.add_header('Content-Type', 'application/json')
            context_request.add_header('Authorization', f'Bearer {token}')
            try:
                request.urlopen(context_request)
            except HTTPError as ex:
                if ex.getcode() in [500]:
                    time.sleep(2) # retry after 2 seconds
                else:
                    raise
        try:
            req = request.Request(f'{api_endpoint}/api/{task_id}/status')
            req.add_header('Content-Type', 'application/json')
            req.add_header('Authorization', f'Bearer {token}')
            return json.loads(request.urlopen(req).read())
        except HTTPError as ex:
            if ex.getcode() in [400, 404, 500]:
                time.sleep(2)
            else:
                raise
        finally:
            watchdog = watchdog + 1

parser = argparse.ArgumentParser()
parser.add_argument('-a', '--attach', default=False, action='store_true', help="return the remote action exit code and output")
parser.add_argument('-d', '--detach', default=False, action='store_true', help="do not wait for completion, instead return the remote task identifier immediately")
parser.add_argument('--hidden', default=False, action='store_true', help="run the action without UI notification")
parser.add_argument('agent', help="agent that runs the action. E.g. \"cluster\"")
parser.add_argument('action', help="action to run. E.g. \"list-actions\"")
parser.add_argument('data', nargs='?', help="JSON input data for the action, or \"-\" to read it from standard input")

args = parser.parse_args()

# Load config from config DB
config = json.loads(subprocess.check_output(['/sbin/e-smith/config', 'getjson', 'ns8']))

# Prepare login credentials
loginobj = {
    "username": config['props']['User'],
    "password": config['props']['Password'],
}
data=json.dumps(loginobj).encode('utf8')

api_endpoint = 'http://' + config["props"]["LeaderIpAddress"] + ':9311'

# POST login request
req = request.Request(f'{api_endpoint}/api/login', data=data)
req.add_header('Content-Type', 'application/json')
resp = request.urlopen(req, timeout = 20)

payload = json.loads(resp.read())

if args.data is None: # send empty string
    input_data = None
elif args.data == '-': # read JSON from stdin
    input_data = json.load(sys.stdin)
else:
    input_data = json.loads(args.data)

if args.agent == 'wait':
    # Wait for the given task
    ret = wait_task(api_endpoint, payload['token'], args.action)
else:
    # Execute action
    task_id = run_task(api_endpoint, payload['token'], args.agent, args.action, input_data)

    if args.detach:
        print(task_id)
        sys.exit(0)

    ret = wait_task(api_endpoint, payload['token'], task_id)

if args.attach:
    json.dump(ret['data']['output'], fp=sys.stdout)
    sys.stderr.write(ret['data']['error'])
    sys.exit(int(ret['data']['exit_code']))
else:
    print(json.dumps(ret))
