#!/usr/bin/env python
# Copyright (c) 2021 VMware, Inc. All Rights Reserved.
# SPDX-License-Identifier: BSD-2 License
# The full license information can be found in LICENSE.txt
# in the root directory of this project.

import collections
import logging
import os
import time
import uuid
import time
import re

from lydian.apps.base import exposify
from lydian.apps.console import Console
from lydian.apps.internal.setup import SetupDB
from lydian.common.background import BackgroundMixin
from lydian.utils.common import get_host_name

log = logging.getLogger(__name__)


@exposify
class Lynis(Console, BackgroundMixin):
    NAME = "Lynis"
    INTERVAL = 5

    LYNIS_PATH = '/tmp/lynis'
    LYNIS_TOOL = 'lynis'
    LYNIS_ARGS = ' audit system -Q'
    INTERVAL = 5

    def __init__(self):
        Console.__init__(self)
        BackgroundMixin.__init__(self)
        self.requests = collections.defaultdict()
        self.done = set()
        self._run = self.run
        self.on()

    def is_running(self):
        """
        Tells if app is available.
        """
        return True

    def start_scan(self, reqid=None):
        """
        """
        if reqid in self.requests:
            log.info('Lynis scan for %s is already running', reqid)
            return
        reqid = reqid or '%s' % uuid.uuid4()
        cmnd = './%s %s' % (os.path.join(self.LYNIS_TOOL),
                          self.LYNIS_ARGS)
        cwd = self.LYNIS_PATH
        p = self._start_subprocess(cmnd, cwd = cwd)
        self.requests[reqid] = p
        return reqid

    def is_scan_complete(self, reqid):
        if reqid not in self.requests:
            log.info('Lynis - Invalid request %s', reqid)
            return None
        return not self._is_alive(self.requests[reqid])

    def stop_scan(self, reqid):
        if reqid not in self.requests:
            log.info('Lynis - Invalid request %s', reqid)
            return None
        if self._is_alive(self.requests[reqid]):  # Still running
            self._kill_subprocess(self.requests[reqid], True)

    def _parsing_output(self, output):
        style_ansi_removal = re.compile(r's/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g')
        res = style_ansi_removal.sub('', output)
        output = res
        # output = re.sub("s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g", "", output)
        # print(output)
        # x = self.run_command("echo " + output + " | sed  \"s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[mGK]//g\" | tr -dc '[[:print:]]\n'")
        # print(x)
        lines = output.split('\n')
        vulns = list()

        for line in lines:
            line = re.sub(r'\[[0-9]+C'," ",line)
            if "WARNING" in line:
                line = line.replace("[ WARNING ]", "")
                line = line.replace("-"," ")
                line = line.strip()
                vulns.append(line)
        return vulns

    def _save_results_to_db(self, reqid):

        proc = self.requests.get(reqid, None)
        if not proc:
            return

        stdout_val = proc.communicate()[0]
        output = stdout_val.strip().decode('utf-8')
        parsed_output = self._parsing_output(output)
        # TODO parse output here:
        with SetupDB() as db:
            db.table = db.RISKS
            for msg in parsed_output:
                db.write(host=get_host_name(), tool='lynis', reqid=reqid,
                         severity='HIGH',
                         message=msg)

    def run(self):
        while not self.stopped:
            for reqid, _ in self.requests.items():
                if self.is_scan_complete(reqid) and reqid not in self.done:
                    try:
                        self._save_results_to_db(reqid)
                    except Exception:
                        log.error("Error in parsing/saving reqults for %s", reqid)
                    finally:
                        self.done.add(reqid)
            time.sleep(self.INTERVAL)

    def get_result(self, reqid):
        with LydianDB() as db:
            db.table = db.RISKS
            return db.read(reqid=reqid)
