import click
import click_log
import logging
import yaml
import json
import jsonschema
from datetime import datetime, timedelta
from pkg_resources import resource_filename
from convisoappsec.flowcli import help_option
from convisoappsec.flowcli.context import pass_flow_context
from convisoappsec.flowcli.common import asset_id_option
from convisoappsec.flowcli.requirements_verifier import RequirementsVerifier
from convisoappsec.flowcli.companies.ls import Companies

logger = logging.getLogger(__name__)
click_log.basic_config(logger)


@click.command('assert-security-rules')
@click_log.simple_verbosity_option(logger)
@asset_id_option(
    required=False
)
@click.option(
    "--company-id",
    required=False,
    envvar=("CONVISO_COMPANY_ID", "FLOW_COMPANY_ID"),
    help="Company ID on Conviso Platform",
)
@click.option(
    "-r",
    "--repository-dir",
    default=".",
    show_default=True,
    type=click.Path(
        exists=True,
        resolve_path=True,
    ),
    required=False,
    help="The source code repository directory.",
)
@click.option(
    '--rules-file',
    'rules_file',
    type=click.File('r'),
    required=True
)
@click.option(
    '--asset-name',
    required=False,
    envvar=("CONVISO_ASSET_NAME", "FLOW_ASSET_NAME"),
    help="Provides a asset name.",
)
@help_option
@pass_flow_context
@click.pass_context
def assert_security_rules(
        context, flow_context, asset_id, company_id, repository_dir, rules_file, asset_name
):
    prepared_context = RequirementsVerifier.prepare_context(context)
    asset_id = prepared_context.params['asset_id']

    if company_id is None:
        companies = Companies()
        company = companies.ls(flow_context, company_id=company_id)
        company_id = company[0]['id']

    try:
        rules = yaml.load(
            rules_file,
            Loader=yaml.Loader
        )

        click.secho(
            '💬 Starting vulnerability security rules assertion...',
            bold=True
        )

        click.secho(
            "💬 Applying the given rules at the security gate:\n{0}".format(yaml.dump(rules)),
            bold=True
        )

        conviso_api = flow_context.create_conviso_graphql_client()
        statuses = ['IDENTIFIED', 'IN_PROGRESS', 'AWAITING_VALIDATION']

        if validate_json(rules)[0] is False:
            msg = click.secho(
                '💬 Error: Validation of the security gate YAML file failed during the validation step!',
                bold=True
            )

            return msg

        current_date = datetime.now()
        default_end_date = datetime(
            current_date.year, current_date.month, current_date.day, 23, 59, 59
        )

        tolerated_days_exist = any(
            'max_days_to_fix' in severity for rule in rules['rules'] for severity in rule['severity'].values()
        )

        issues_set = set()

        if tolerated_days_exist:
            tolerated_days_and_severity = tolerated_days_by_severity(rules)
            for severity, days in tolerated_days_and_severity.items():
                end_date = default_end_date - timedelta(days=days)
                end_date = end_date.isoformat() + "Z"
                severity_issues = conviso_api.issues.get_issues_stats(
                    asset_id, company_id, statuses, end_date=end_date
                )
                for issue in severity_issues:
                    severity = severity.upper()

                    if issue['value'] == severity:
                        issues_set.add((severity, issue["count"]))

            issues = [{'value': value, 'count': count} for value, count in issues_set]
        else:
            issues = conviso_api.issues.get_issues_stats(asset_id, company_id, statuses)

        response = validate_rules(issues, rules)

        __raise_if_gate_locked(response)

        click.secho(
            '✅ Vulnerability security rules assertion finished.',
            bold=True
        )
    except Exception as e:
        raise click.ClickException(str(e)) from e


def tolerated_days_by_severity(rules):
    """
    Returns tolerated days for each severity to facilitate validation.
    """
    days_by_severity = {}

    for rule in rules['rules']:
        for severity, values in rule['severity'].items():
            if 'max_days_to_fix' in values:
                days_by_severity[severity] = values['max_days_to_fix']
            else:
                days_by_severity[severity] = 0

    return days_by_severity


def __raise_if_gate_locked(response):
    if response['locked']:
        click.secho('💬 Vulnerabilities summary...', bold=True)

        logger.info(
            json.dumps(response['summary'], indent=4)
        )

        raise click.ClickException(
            'Vulnerabilities quantity offending security rules.'
        )


def validate_rules(issues, rules):
    """ function to validate security gate rules """
    response = {"locked": False, "summary": [{"from": "any", "severity": {}}]}
    parsed_issues = {issue['value']: issue['count'] for issue in issues}

    for i, rule in enumerate(rules['rules']):
        for criticity, rule_max in rule['severity'].items():
            if parsed_issues[criticity.upper()] > rule_max['maximum']:
                response['locked'] = True
            response["summary"][i]["severity"].update({criticity: {"quantity": parsed_issues[criticity.upper()]}})

    return response


def validate_json(rules):
    """ Validate a JSON document against a JSON schema. """
    schema_path = resource_filename('convisoappsec', 'flowcli/vulnerability/rules_schema.json')

    with open(schema_path, 'r') as json_file:
        schema = json.load(json_file)
    try:
        jsonschema.validate(rules, schema)
        return True, "Validation successful"
    except jsonschema.exceptions.ValidationError as e:
        return False, str(e)


EPILOG = '''
'''

SHORT_HELP = ''

command = 'conviso vulnerability assert-security-rules'
assert_security_rules.short_help = SHORT_HELP
assert_security_rules.epilog = EPILOG.format(
    command=command,
)
