.. versionadded:: 2019.2.0

    Ensure a security policy definition exists.

    :param name:
        Name of the policy definition.

    :param policy_rule:
        A YAML dictionary defining the policy rule. See `Azure Policy Definition documentation
        <https://docs.microsoft.com/en-us/azure/azure-policy/policy-definition#policy-rule>`_ for details on the
        structure. One of ``policy_rule``, ``policy_rule_json``, or ``policy_rule_file`` is required, in that order of
        precedence for use if multiple parameters are used.

    :param policy_rule_json:
        A text field defining the entirety of a policy definition in JSON. See `Azure Policy Definition documentation
        <https://docs.microsoft.com/en-us/azure/azure-policy/policy-definition#policy-rule>`_ for details on the
        structure. One of ``policy_rule``, ``policy_rule_json``, or ``policy_rule_file`` is required, in that order of
        precedence for use if multiple parameters are used. Note that the `name` field in the JSON will override the
        ``name`` parameter in the state.

    :param policy_rule_file:
        The source of a JSON file defining the entirety of a policy definition. See `Azure Policy Definition
        documentation <https://docs.microsoft.com/en-us/azure/azure-policy/policy-definition#policy-rule>`_ for
        details on the structure. One of ``policy_rule``, ``policy_rule_json``, or ``policy_rule_file`` is required,
        in that order of precedence for use if multiple parameters are used. Note that the `name` field in the JSON
        will override the ``name`` parameter in the state.

    :param skip_verify:
        Used for the ``policy_rule_file`` parameter. If ``True``, hash verification of remote file sources
        (``http://``, ``https://``, ``ftp://``) will be skipped, and the ``source_hash`` argument will be ignored.

    :param source_hash:
        This can be a source hash string or the URI of a file that contains source hash strings.

    :param source_hash_name:
        When ``source_hash`` refers to a hash file, Salt will try to find the correct hash by matching the
        filename/URI associated with that hash.

    :param policy_type:
        The type of policy definition. Possible values are NotSpecified, BuiltIn, and Custom. Only used with the
        ``policy_rule`` parameter.

    :param mode:
        The policy definition mode. Possible values are NotSpecified, Indexed, and All. Only used with the
        ``policy_rule`` parameter.

    :param display_name:
        The display name of the policy definition. Only used with the ``policy_rule`` parameter.

    :param description:
        The policy definition description. Only used with the ``policy_rule`` parameter.

    :param metadata:
        The policy definition metadata defined as a dictionary. Only used with the ``policy_rule`` parameter.

    :param parameters:
        Required dictionary if a parameter is used in the policy rule. Only used with the ``policy_rule`` parameter.

    :param connection_auth:
        A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure policy definition exists:
            azurearm_resource.policy_definition_present:
                - name: testpolicy
                - display_name: Test Policy
                - description: Test policy for testing policies.
                - policy_rule:
                    if:
                      allOf:
                        - equals: Microsoft.Compute/virtualMachines/write
                          source: action
                        - field: location
                          in:
                            - eastus
                            - eastus2
                            - centralus
                    then:
                      effect: deny
                - connection_auth: {{ profile }}

def policy_definition_present(name, policy_rule=None, policy_type=None, mode=None, display_name=None, description=None,
                              metadata=None, parameters=None, policy_rule_json=None, policy_rule_file=None,
                              template='jinja', source_hash=None, source_hash_name=None, skip_verify=False,
                              connection_auth=None, **kwargs):
    '''
    .. versionadded:: 2019.2.0

    Ensure a security policy definition exists.

    :param name:
        Name of the policy definition.

    :param policy_rule:
        A YAML dictionary defining the policy rule. See `Azure Policy Definition documentation
        <https://docs.microsoft.com/en-us/azure/azure-policy/policy-definition#policy-rule>`_ for details on the
        structure. One of ``policy_rule``, ``policy_rule_json``, or ``policy_rule_file`` is required, in that order of
        precedence for use if multiple parameters are used.

    :param policy_rule_json:
        A text field defining the entirety of a policy definition in JSON. See `Azure Policy Definition documentation
        <https://docs.microsoft.com/en-us/azure/azure-policy/policy-definition#policy-rule>`_ for details on the
        structure. One of ``policy_rule``, ``policy_rule_json``, or ``policy_rule_file`` is required, in that order of
        precedence for use if multiple parameters are used. Note that the `name` field in the JSON will override the
        ``name`` parameter in the state.

    :param policy_rule_file:
        The source of a JSON file defining the entirety of a policy definition. See `Azure Policy Definition
        documentation <https://docs.microsoft.com/en-us/azure/azure-policy/policy-definition#policy-rule>`_ for
        details on the structure. One of ``policy_rule``, ``policy_rule_json``, or ``policy_rule_file`` is required,
        in that order of precedence for use if multiple parameters are used. Note that the `name` field in the JSON
        will override the ``name`` parameter in the state.

    :param skip_verify:
        Used for the ``policy_rule_file`` parameter. If ``True``, hash verification of remote file sources
        (``http://``, ``https://``, ``ftp://``) will be skipped, and the ``source_hash`` argument will be ignored.

    :param source_hash:
        This can be a source hash string or the URI of a file that contains source hash strings.

    :param source_hash_name:
        When ``source_hash`` refers to a hash file, Salt will try to find the correct hash by matching the
        filename/URI associated with that hash.

    :param policy_type:
        The type of policy definition. Possible values are NotSpecified, BuiltIn, and Custom. Only used with the
        ``policy_rule`` parameter.

    :param mode:
        The policy definition mode. Possible values are NotSpecified, Indexed, and All. Only used with the
        ``policy_rule`` parameter.

    :param display_name:
        The display name of the policy definition. Only used with the ``policy_rule`` parameter.

    :param description:
        The policy definition description. Only used with the ``policy_rule`` parameter.

    :param metadata:
        The policy definition metadata defined as a dictionary. Only used with the ``policy_rule`` parameter.

    :param parameters:
        Required dictionary if a parameter is used in the policy rule. Only used with the ``policy_rule`` parameter.

    :param connection_auth:
        A dict with subscription and authentication parameters to be used in connecting to the
        Azure Resource Manager API.

    Example usage:

    .. code-block:: yaml

        Ensure policy definition exists:
            azurearm_resource.policy_definition_present:
                - name: testpolicy
                - display_name: Test Policy
                - description: Test policy for testing policies.
                - policy_rule:
                    if:
                      allOf:
                        - equals: Microsoft.Compute/virtualMachines/write
                          source: action
                        - field: location
                          in:
                            - eastus
                            - eastus2
                            - centralus
                    then:
                      effect: deny
                - connection_auth: {{ profile }}

    '''
    ret = {
        'name': name,
        'result': False,
        'comment': '',
        'changes': {}
    }

    if not isinstance(connection_auth, dict):
        ret['comment'] = 'Connection information must be specified via connection_auth dictionary!'
        return ret

    if not policy_rule and not policy_rule_json and not policy_rule_file:
        ret['comment'] = 'One of "policy_rule", "policy_rule_json", or "policy_rule_file" is required!'
        return ret

    if sum(x is not None for x in [policy_rule, policy_rule_json, policy_rule_file]) > 1:
        ret['comment'] = 'Only one of "policy_rule", "policy_rule_json", or "policy_rule_file" is allowed!'
        return ret

    if ((policy_rule_json or policy_rule_file) and
       (policy_type or mode or display_name or description or metadata or parameters)):
        ret['comment'] = 'Policy definitions cannot be passed when "policy_rule_json" or "policy_rule_file" is defined!'
        return ret

    temp_rule = {}
    if policy_rule_json:
        try:
            temp_rule = json.loads(policy_rule_json)
        except Exception as exc:
            ret['comment'] = 'Unable to load policy rule json! ({0})'.format(exc)
            return ret
    elif policy_rule_file:
        try:
            # pylint: disable=unused-variable
            sfn, source_sum, comment_ = __salt__['file.get_managed'](
                None,
                template,
                policy_rule_file,
                source_hash,
                source_hash_name,
                None,
                None,
                None,
                __env__,
                None,
                None,
                skip_verify=skip_verify,
                **kwargs
            )
        except Exception as exc:
            ret['comment'] = 'Unable to locate policy rule file "{0}"! ({1})'.format(policy_rule_file, exc)
            return ret

        if not sfn:
            ret['comment'] = 'Unable to locate policy rule file "{0}"!)'.format(policy_rule_file)
            return ret

        try:
            with salt.utils.files.fopen(sfn, 'r') as prf:
                temp_rule = json.load(prf)
        except Exception as exc:
            ret['comment'] = 'Unable to load policy rule file "{0}"! ({1})'.format(policy_rule_file, exc)
            return ret

        if sfn:
            salt.utils.files.remove(sfn)

    policy_name = name
    if policy_rule_json or policy_rule_file:
        if temp_rule.get('name'):
            policy_name = temp_rule.get('name')
        policy_rule = temp_rule.get('properties', {}).get('policyRule')
        policy_type = temp_rule.get('properties', {}).get('policyType')
        mode = temp_rule.get('properties', {}).get('mode')
        display_name = temp_rule.get('properties', {}).get('displayName')
        description = temp_rule.get('properties', {}).get('description')
        metadata = temp_rule.get('properties', {}).get('metadata')
        parameters = temp_rule.get('properties', {}).get('parameters')

    policy = __salt__['azurearm_resource.policy_definition_get'](name, azurearm_log_level='info', **connection_auth)

    if 'error' not in policy:
        if policy_type and policy_type.lower() != policy.get('policy_type', '').lower():
            ret['changes']['policy_type'] = {
                'old': policy.get('policy_type'),
                'new': policy_type
            }

        if (mode or '').lower() != policy.get('mode', '').lower():
            ret['changes']['mode'] = {
                'old': policy.get('mode'),
                'new': mode
            }

        if (display_name or '').lower() != policy.get('display_name', '').lower():
            ret['changes']['display_name'] = {
                'old': policy.get('display_name'),
                'new': display_name
            }

        if (description or '').lower() != policy.get('description', '').lower():
            ret['changes']['description'] = {
                'old': policy.get('description'),
                'new': description
            }

        rule_changes = __utils__['dictdiffer.deep_diff'](policy.get('policy_rule', {}), policy_rule or {})
        if rule_changes:
            ret['changes']['policy_rule'] = rule_changes

        meta_changes = __utils__['dictdiffer.deep_diff'](policy.get('metadata', {}), metadata or {})
        if meta_changes:
            ret['changes']['metadata'] = meta_changes

        param_changes = __utils__['dictdiffer.deep_diff'](policy.get('parameters', {}), parameters or {})
        if param_changes:
            ret['changes']['parameters'] = param_changes

        if not ret['changes']:
            ret['result'] = True
            ret['comment'] = 'Policy definition {0} is already present.'.format(name)
            return ret

        if __opts__['test']:
            ret['comment'] = 'Policy definition {0} would be updated.'.format(name)
            ret['result'] = None
            return ret

    else:
        ret['changes'] = {
            'old': {},
            'new': {
                'name': policy_name,
                'policy_type': policy_type,
                'mode': mode,
                'display_name': display_name,
                'description': description,
                'metadata': metadata,
                'parameters': parameters,
                'policy_rule': policy_rule,
            }
        }

    if __opts__['test']:
        ret['comment'] = 'Policy definition {0} would be created.'.format(name)
        ret['result'] = None
        return ret

    # Convert OrderedDict to dict
    if isinstance(metadata, dict):
        metadata = json.loads(json.dumps(metadata))
    if isinstance(parameters, dict):
        parameters = json.loads(json.dumps(parameters))

    policy_kwargs = kwargs.copy()
    policy_kwargs.update(connection_auth)

    policy = __salt__['azurearm_resource.policy_definition_create_or_update'](
        name=policy_name,
        policy_rule=policy_rule,
        policy_type=policy_type,
        mode=mode,
        display_name=display_name,
        description=description,
        metadata=metadata,
        parameters=parameters,
        **policy_kwargs
    )

    if 'error' not in policy:
        ret['result'] = True
        ret['comment'] = 'Policy definition {0} has been created.'.format(name)
        return ret

    ret['comment'] = 'Failed to create policy definition {0}! ({1})'.format(name, policy.get('error'))
    return ret