#!/usr/bin/env python3

# Copyright (C) 2020  Braiins Systems s.r.o.
#
# This file is part of Braiins Open-Source Initiative (BOSI).
#
# BOSI 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
# (at your option) any later version.
#
# This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
#
# Please, keep in mind that we may also license BOSI or any part thereof
# under a proprietary license. For more information on the terms and conditions
# of such proprietary license or if you have any other questions, please
# contact us at opensource@braiins.com.

import argparse
import concurrent.futures
import logging
import sys
import threading
import warnings

# these are needed for ssh method
from bos_toolbox.batch import read_hosts
from bos_utils.ssh import SSHManager
from subprocess import CalledProcessError


LOG = logging.getLogger(__name__)


def main(parser, args):
    hosts = read_hosts(args.hosts)

    if args.command == ['start']:
        command = '/etc/init.d/bosminer start'
    elif args.command == ['stop']:
        command = '/etc/init.d/bosminer stop'
    elif args.command == ['restart']:
        command = '/etc/init.d/bosminer restart'
    elif args.command == ['nightly-on']:
        command = 'miner nightly_feeds on'
    elif args.command == ['nightly-off']:
        command = 'miner nightly_feeds off'
    elif args.command == ['led-on']:
        command = 'miner fault_light on'
    elif args.command == ['led-off']:
        command = 'miner fault_light off'
    elif args.command == ['reset-tuner']:
        command = 'rm /etc/bosminer-autotune.json && /etc/init.d/bosminer restart'
    elif args.command == ['disconnect-farm']:
        command = '/etc/init.d/bosminer restart'
    elif args.command == ['set-dhcp']:
        command = 'uci set network.lan.proto=dhcp && uci commit network && /etc/init.d/network restart'
    elif args.command == ['immersion-on']:
        command = 'grep -q "min_fans" /etc/bosminer.toml; if [ $? -eq 1 ]; then echo -e "[fan_control]\nmin_fans = 0" >> /etc/bosminer.toml; else sed -i "/min_fans/c\min_fans = 0" /etc/bosminer.toml; fi && /etc/init.d/bosminer restart'
    elif args.command == ['immersion-off']:
        command = 'sed -i "/fan_control/d" /etc/bosminer.toml; sed -i "/min_fans/d" /etc/bosminer.toml && /etc/init.d/bosminer restart'
    else:
        command = ' '.join(args.command)

    runner = catcher_logger(run_command_ssh)

    if args.jobs <= 1:
        # avoid executor swalowing exceptions
        for host in hosts:
            runner(host, 'root', args.password, command, args)
    else:
        with concurrent.futures.ThreadPoolExecutor(max_workers=args.jobs) as executor:
            for host in hosts:
                executor.submit(runner, host, 'root', args.password, command, args)


def catcher_logger(fce):
    def catcher_logger_wrapper(*args, **kargs):
        try:
            fce(*args, **kargs)
        except Exception as ex:
            LOG.error(str(ex))

    return catcher_logger_wrapper


printer_lock = threading.Lock()


# @catcher_logger
def run_command_ssh(host, user, password, command, args):
    with SSHManager(
        host, user, password, load_host_keys=False, get_pass=False
    ) as client:
        try:
            stdout, stderr = client.run(command)
        except CalledProcessError as ex:
            stdout = ex.stdout
            stderr = ex.stderr
        with printer_lock:
            if args.print_host:
                LOG.info(host)
            if args.output:
                print(
                    stdout.read().decode('latin1'), end='', file=sys.stdout, flush=True
                )
                print(
                    stderr.read().decode('latin1'), end='', file=sys.stderr, flush=True
                )


def build_arg_parser(parser, desc):
    label = desc['label']
    hlp = desc['help']

    parser.description = desc['description']

    parser.add_argument(
        'hosts', metavar=label['hosts'], help=hlp['hosts'], widget='FileChooser'
    )
    parser.add_argument(
        'command', nargs='+', metavar=label['command'], help=hlp['command']
    )
    parser.add_argument(
        '-o',
        '--output',
        action='store_true',
        metavar=label['output'],
        help=hlp['output'],
    )
    parser.add_argument(
        '-O',
        '--output-hostname',
        dest='print_host',
        action='store_true',
        metavar=label['output-hostname'],
        help=hlp['output-hostname'],
    )
    parser.add_argument(
        '-p',
        '--password',
        default='',
        metavar=label['password'],
        help=hlp['password'],
        widget='PasswordField',
    )
    parser.add_argument(
        '-j',
        '--jobs',
        type=int,
        default=1,
        metavar=label['jobs'],
        help=hlp['jobs'],
        gooey_options={'visible': True},
    )


if __name__ == '__main__':
    warnings.filterwarnings('ignore', module='paramiko')
    parser = argparse.ArgumentParser()
    build_arg_parser(parser)
    args = parser.parse_args()
    main(parser, args)
