import yaml
import sys
import argparse
import tempfile
import subprocess
from modules.ssa_utils import *
from modules.testing_utils import *


DUMB_PIPELINE_INPUT = '| from read_text("/")' \
                      '| select from_json_object(value) as input_event' \
                      '| eval timestamp=parse_long(ucast(map_get(input_event, "_time"), "string", null))'

DUMB_PIPELINE_OUTPUT = '| select start_time, end_time, entities, body;'


def main(args):
    parser = argparse.ArgumentParser()
    parser.add_argument('--skip-errors', action='store_true', default=False)
    parser.add_argument('--debug', action='store_true', default=False)
    parser.add_argument('detection_files', type=str, nargs='+', help="detection files to be checked")
    parsed = parser.parse_args(args)
    if parsed.debug:
        logger.setLevel(logging.DEBUG)
    build_humvee(get_path(SSML_CWD))
    status = True
    passed_validations = []
    failed_validations = []
    for detection_file in parsed.detection_files:
        if os.path.isfile(detection_file):
            with open(detection_file, 'r') as detection_fh:
                detection = yaml.safe_load(detection_fh)
                if detection['type'] == "SSA":
                    # Parsed file is a SSA detection
                    log(logging.INFO, "Validating %s" % detection['name'])
                    cur_status = validate_tags(detection)
                    status = status & cur_status
                    if cur_status:
                        passed_validations.append(detection_file)
                    else:
                        failed_validations.append(detection_file)
                    if not status and not parsed.skip_errors:
                        exit(1)
    if status:
        exit(0)
    else:
        exit(1)


def write_validation_pipeline(spl2, spl2_file):
    pipeline = re.sub(r"\|\s*from\s+read_ssa_enriched_events\(\s*\)",
                      DUMB_PIPELINE_INPUT,
                      spl2)
    pipeline = re.sub(r"\|\s*into\s+write_ssa_detected_events\(\s*\)\s*;",
                      DUMB_PIPELINE_OUTPUT,
                      pipeline)
    with open(spl2_file, 'w') as spl2_fh:
        spl2_fh.write(pipeline)
    log(logging.DEBUG, "Testing SPL2 pipeline", detail=pipeline)


def extract_ssa_fields(spl2):
    """
    From a SPL2 pipeline extracts SSA fields using Humvee
    @param spl2: String representing the pipeline search
    @return: A set of fields used in the pipeline
    """
    data_dir = tempfile.TemporaryDirectory(prefix="data", dir=get_path("%s" % SSML_CWD))
    pipeline_file = os.path.join(data_dir.name, "test.spl2")
    fields_file = os.path.join(data_dir.name, "fields.out")
    write_validation_pipeline(spl2, pipeline_file)
    subprocess.run(["/usr/bin/java",
                    "-jar", get_path("%s/humvee.jar" % SSML_CWD),
                    'cli', '-i',
                    pipeline_file, '-o',
                    fields_file,
                    '-f'],
                   stderr=subprocess.DEVNULL,
                   check=True)
    spl2_ssa_fields = set()
    with open(fields_file, 'r') as test_out_fh:
        for f in test_out_fh.readlines():
            spl2_ssa_fields.add(f.strip())
    return spl2_ssa_fields


def validate_tags(detection):
    """
    Checks that some generic tags have been populated
    @param detection: Parsed YAML dictionary of a SSA detection file
    @return: True when tags are present
    """
    if 'tags' not in detection:
        log(logging.ERROR, "Missing `tags` from detection %s" % detection['name'])
        return False
    if 'risk_severity' not in detection['tags']:
        log(logging.ERROR, "Missing `risk_severity` tag from detection `tags` in %s" % detection['name'])
        return False
    return validate_required_fields(detection)


def validate_required_fields(detection):
    """
    Checks that required fields has been populated in the required tags.
    If there are issues it reports them to the log ERROR console
    @param detection: Parsed YAML dictionary of a SSA detection file
    @return: True when declared fields are consistent with the search
    """
    try:
        spl2_ssa_fields = extract_ssa_fields(detection['search'])
        log(logging.DEBUG, "SSA fields used by the detection", detail=",".join(spl2_ssa_fields))
        if "required_fields" not in detection['tags']:
            log(logging.ERROR,
                "required_fields not present in detection %s" % detection['name'],
                detail='''Suggested action: Append this to "tags" in your detection
        %s''' % yaml.dump({'required_fields': list(spl2_ssa_fields)}))
            return False
        declared_ssa_fields = set(detection['tags']['required_fields'])
        fields_declared_not_used = declared_ssa_fields.difference(spl2_ssa_fields)
        fields_not_declared = spl2_ssa_fields.difference(declared_ssa_fields)
        if len(fields_declared_not_used) > 0:
            log(logging.ERROR,
                "Some declared fields in detection %s not used in pipeline" % detection['name'],
                detail=','.join(fields_declared_not_used))
            return False
        if len(fields_not_declared) > 0:
            log(logging.ERROR,
                "Some fields used in the pipeline not declared in detection %s" % detection['name'],
                detail=','.join(fields_not_declared))
            return False
        return True
    except subprocess.CalledProcessError:
        log(logging.ERROR,
            "Syntax errors in pipeline %s" % detection['name'],
            detail=detection['search'])
        log(logging.INFO, "Perhaps required [input|output] fields do not match SSA ones")
    return False


if __name__ == '__main__':
    main(sys.argv[1:])
