#!/usr/bin/python
# -*- coding: utf-8 -*-

# Copyright: (c) 2020, Kevin Breit (@kbreit) <kevin.breit@kevinbreit.net>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import absolute_import, division, print_function
__metaclass__ = type

ANSIBLE_METADATA = {
    'metadata_version': '1.1',
    'status': ['preview'],
    'supported_by': 'community'
}

DOCUMENTATION = r'''
---
module: meraki_ms_ospf
short_description: Manage OSPF configuration on MS switches
description:
- Configure OSPF for compatible Meraki MS switches.
options:
    state:
        description:
        - Read or edit OSPF settings.
        type: str
        choices: [ present, query ]
        default: present
    net_name:
        description:
        - Name of network containing OSPF configuration.
        type: str
        aliases: [ name, network ]
    net_id:
        description:
        - ID of network containing OSPF configuration.
        type: str
    enabled:
        description:
        - Enable or disable OSPF on the network.
        type: bool
    hello_timer:
        description:
        - Time interval, in seconds, at which hello packets will be sent to OSPF neighbors to maintain connectivity.
        - Value must be between 1 and 255.
        - Default is 10 seconds.
        type: int
    dead_timer:
        description:
        - Time interval to determine when the peer will be declared inactive.
        - Value must be between 1 and 65535.
        type: int
    md5_authentication_enabled:
        description:
        - Whether to enable or disable MD5 authentication.
        type: bool
    md5_authentication_key:
        description:
        - MD5 authentication credentials.
        type: dict
        suboptions:
            id:
                description:
                - MD5 authentication key index.
                - Must be between 1 and 255.
                type: str
            passphrase:
                description:
                - Plain text authentication passphrase
                type: str
    areas:
        description:
        - List of areas in OSPF network.
        type: list
        elements: dict
        suboptions:
            area_id:
                description:
                - OSPF area ID
                type: int
                aliases: [ id ]
            area_name:
                description:
                - Descriptive name of OSPF area.
                type: str
                aliases: [ name ]
            area_type:
                description:
                - OSPF area type.
                choices: [normal, stub, nssa]
                type: str
                aliases: [ type ]
author:
- Kevin Breit (@kbreit)
extends_documentation_fragment: cisco.meraki.meraki
'''

EXAMPLES = r'''
  - name: Query OSPF settings
    meraki_ms_ospf:
      auth_key: abc123
      org_name: YourOrg
      net_name: YourNet
      state: query
    delegate_to: localhost

  - name: Enable OSPF with check mode
    meraki_ms_ospf:
      auth_key: abc123
      org_name: YourOrg
      net_name: YourNet
      state: present
      enabled: true
      hello_timer: 20
      dead_timer: 60
      areas:
        - area_id: 0
          area_name: Backbone
          area_type: normal
        - area_id: 1
          area_name: Office
          area_type: nssa
      md5_authentication_enabled: false
'''

RETURN = r'''
data:
    description: Information about queried object.
    returned: success
    type: complex
    contains:
        enabled:
            description:
            - Enable or disable OSPF on the network.
            type: bool
        hello_timer_in_seconds:
            description:
            - Time interval, in seconds, at which hello packets will be sent to OSPF neighbors to maintain connectivity.
            type: int
        dead_timer_in_seconds:
            description:
            - Time interval to determine when the peer will be declared inactive.
            type: int
        areas:
            description:
            - List of areas in OSPF network.
            type: complex
            contains:
                area_id:
                    description:
                    - OSPF area ID
                    type: int
                area_name:
                    description:
                    - Descriptive name of OSPF area.
                    type: str
                area_type:
                    description:
                    - OSPF area type.
                    type: str
        md5_authentication_enabled:
            description:
            - Whether to enable or disable MD5 authentication.
            type: bool
        md5_authentication_key:
            description:
            - MD5 authentication credentials.
            type: complex
            contains:
                id:
                    description:
                    - MD5 key index.
                    type: int
                passphrase:
                    description:
                    - Passphrase for MD5 key.
                    type: str
'''

from ansible.module_utils.basic import AnsibleModule, json
from ansible_collections.cisco.meraki.plugins.module_utils.network.meraki.meraki import MerakiModule, meraki_argument_spec


def construct_payload(meraki):
    payload_key_mapping = {'enabled': 'enabled',
                           'hello_timer': 'helloTimerInSeconds',
                           'dead_timer': 'deadTimerInSeconds',
                           'areas': 'areas',
                           'area_id': 'areaId',
                           'area_name': 'areaName',
                           'area_type': 'areaType',
                           'md5_authentication_enabled': 'md5AuthenticationEnabled',
                           'md5_authentication_key': 'md5AuthenticationKey',
                           'id': 'id',
                           'passphrase': 'passphrase',
                           }
    payload = {}

    # This may need to be reworked to avoid overwiting
    for snake, camel in payload_key_mapping.items():
        try:
            if meraki.params[snake] is not None:
                payload[camel] = meraki.params[snake]
                if snake == 'areas':
                    if meraki.params['areas'] is not None and len(meraki.params['areas']) > 0:
                        payload['areas'] = []
                        for area in meraki.params['areas']:
                            area_settings = {'areaName': area['area_name'],
                                             'areaId': area['area_id'],
                                             'areaType': area['area_type'],
                                             }
                            payload['areas'].append(area_settings)
                elif snake == 'md5_authentication_key':
                    if meraki.params['md5_authentication_key'] is not None:
                        md5_settings = {'id': meraki.params['md5_authentication_key']['id'],
                                        'passphrase': meraki.params['md5_authentication_key']['passphrase'],
                                        }
        except KeyError:
            pass

    return payload


def main():
    # define the available arguments/parameters that a user can pass to
    # the module

    areas_arg_spec = dict(area_id=dict(type='int', aliases=['id']),
                          area_name=dict(type='str', aliases=['name']),
                          area_type=dict(type='str', aliases=['type'], choices=['normal', 'stub', 'nssa']),
                          )

    md5_auth_arg_spec = dict(id=dict(type='str'),
                             passphrase=dict(type='str', no_log=True),
                             )

    argument_spec = meraki_argument_spec()
    argument_spec.update(state=dict(type='str', choices=['present', 'query'], default='present'),
                         net_id=dict(type='str'),
                         net_name=dict(type='str', aliases=['name', 'network']),
                         enabled=dict(type='bool'),
                         hello_timer=dict(type='int'),
                         dead_timer=dict(type='int'),
                         areas=dict(type='list', default=None, elements='dict', options=areas_arg_spec),
                         md5_authentication_enabled=dict(type='bool'),
                         md5_authentication_key=dict(type='dict', default=None, options=md5_auth_arg_spec),
                         )

    # the AnsibleModule object will be our abstraction working with Ansible
    # this includes instantiation, a couple of common attr would be the
    # args/params passed to the execution, as well as if the module
    # supports check mode
    module = AnsibleModule(argument_spec=argument_spec,
                           supports_check_mode=True,
                           )
    meraki = MerakiModule(module, function='ms_ospf')

    meraki.params['follow_redirects'] = 'all'

    query_urls = {'ms_ospf': '/networks/{net_id}/switch/routing/ospf'}
    update_urls = {'ms_ospf': '/networks/{net_id}/switch/routing/ospf'}

    meraki.url_catalog['get_all'].update(query_urls)
    meraki.url_catalog['update'] = update_urls

    payload = None

    # execute checks for argument completeness

    if meraki.params['dead_timer'] is not None:
        if meraki.params['dead_timer'] < 1 or meraki.params['dead_timer'] > 65535:
            meraki.fail_json(msg='dead_timer must be between 1 and 65535')
    if meraki.params['hello_timer'] is not None:
        if meraki.params['hello_timer'] < 1 or meraki.params['hello_timer'] > 255:
            meraki.fail_json(msg='hello_timer must be between 1 and 65535')
    if meraki.params['md5_authentication_enabled'] is False:
        if meraki.params['md5_authentication_key'] is not None:
            meraki.fail_json(msg='md5_authentication_key must not be configured when md5_authentication_enabled is false')

    # manipulate or modify the state as needed (this is going to be the
    # part where your module will do what it needs to do)

    org_id = meraki.params['org_id']
    if not org_id:
        org_id = meraki.get_org_id(meraki.params['org_name'])
    net_id = meraki.params['net_id']
    if net_id is None and meraki.params['net_name']:
        nets = meraki.get_nets(org_id=org_id)
        net_id = meraki.get_net_id(net_name=meraki.params['net_name'], data=nets)
    if meraki.params['state'] == 'query':
        path = meraki.construct_path('get_all', net_id=net_id)
        response = meraki.request(path, method='GET')
        meraki.result['data'] = response
        meraki.exit_json(**meraki.result)
    elif meraki.params['state'] == 'present':
        original = meraki.request(meraki.construct_path('get_all', net_id=net_id), method='GET')
        payload = construct_payload(meraki)
        if meraki.is_update_required(original, payload) is True:
            if meraki.check_mode is True:
                meraki.result['data'] = payload
                meraki.result['changed'] = True
                meraki.exit_json(**meraki.result)
            path = meraki.construct_path('update', net_id=net_id)
            response = meraki.request(path, method='PUT', payload=json.dumps(payload))
            if 'md5_authentication_key' in response:
                response['md5_authentication_key']['passphrase'] = 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'
            meraki.result['data'] = response
            meraki.result['changed'] = True
            meraki.exit_json(**meraki.result)
        else:
            if 'md5_authentication_key' in original:
                original['md5_authentication_key']['passphrase'] = 'VALUE_SPECIFIED_IN_NO_LOG_PARAMETER'
            meraki.result['data'] = original
            meraki.exit_json(**meraki.result)

    # in the event of a successful module execution, you will want to
    # simple AnsibleModule.exit_json(), passing the key/value results
    meraki.exit_json(**meraki.result)


if __name__ == '__main__':
    main()
