#!/usr/bin/python
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.

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


DOCUMENTATION = '''
---
module: ec2_vpc_nat_gateway
short_description: Manage AWS VPC NAT Gateways.
description:
  - Ensure the state of AWS VPC NAT Gateways based on their id, allocation and subnet ids.
version_added: "2.2"
requirements: [boto3, botocore]
options:
  state:
    description:
      - Ensure NAT Gateway is present or absent.
    required: false
    default: "present"
    choices: ["present", "absent"]
  nat_gateway_id:
    description:
      - The id AWS dynamically allocates to the NAT Gateway on creation.
        This is required when the absent option is present.
    required: false
    default: None
  subnet_id:
    description:
      - The id of the subnet to create the NAT Gateway in. This is required
        with the present option.
    required: false
    default: None
  allocation_id:
    description:
      - The id of the elastic IP allocation. If this is not passed and the
        eip_address is not passed. An EIP is generated for this NAT Gateway.
    required: false
    default: None
  eip_address:
    description:
      - The elastic IP address of the EIP you want attached to this NAT Gateway.
        If this is not passed and the allocation_id is not passed,
        an EIP is generated for this NAT Gateway.
    required: false
  if_exist_do_not_create:
    description:
      - if a NAT Gateway exists already in the subnet_id, then do not create a new one.
    required: false
    default: false
  release_eip:
    description:
      - Deallocate the EIP from the VPC.
      - Option is only valid with the absent state.
      - You should use this with the wait option. Since you can not release an address while a delete operation is happening.
    required: false
    default: true
  wait:
    description:
      - Wait for operation to complete before returning.
    required: false
    default: false
  wait_timeout:
    description:
      - How many seconds to wait for an operation to complete before timing out.
    required: false
    default: 300
  client_token:
    description:
      - Optional unique token to be used during create to ensure idempotency.
        When specifying this option, ensure you specify the eip_address parameter
        as well otherwise any subsequent runs will fail.
    required: false

author:
  - "Allen Sanabria (@linuxdynasty)"
  - "Jon Hadfield (@jonhadfield)"
  - "Karen Cheng(@Etherdaemon)"
extends_documentation_fragment:
  - aws
  - ec2
'''

EXAMPLES = '''
# Note: These examples do not set authentication details, see the AWS Guide for details.

- name: Create new nat gateway with client token.
  ec2_vpc_nat_gateway:
    state: present
    subnet_id: subnet-12345678
    eip_address: 52.1.1.1
    region: ap-southeast-2
    client_token: abcd-12345678
  register: new_nat_gateway

- name: Create new nat gateway using an allocation-id.
  ec2_vpc_nat_gateway:
    state: present
    subnet_id: subnet-12345678
    allocation_id: eipalloc-12345678
    region: ap-southeast-2
  register: new_nat_gateway

- name: Create new nat gateway, using an EIP address  and wait for available status.
  ec2_vpc_nat_gateway:
    state: present
    subnet_id: subnet-12345678
    eip_address: 52.1.1.1
    wait: yes
    region: ap-southeast-2
  register: new_nat_gateway

- name: Create new nat gateway and allocate new EIP.
  ec2_vpc_nat_gateway:
    state: present
    subnet_id: subnet-12345678
    wait: yes
    region: ap-southeast-2
  register: new_nat_gateway

- name: Create new nat gateway and allocate new EIP if a nat gateway does not yet exist in the subnet.
  ec2_vpc_nat_gateway:
    state: present
    subnet_id: subnet-12345678
    wait: yes
    region: ap-southeast-2
    if_exist_do_not_create: true
  register: new_nat_gateway

- name: Delete nat gateway using discovered nat gateways from facts module.
  ec2_vpc_nat_gateway:
    state: absent
    region: ap-southeast-2
    wait: yes
    nat_gateway_id: "{{ item.NatGatewayId }}"
    release_eip: yes
  register: delete_nat_gateway_result
  with_items: "{{ gateways_to_remove.result }}"

- name: Delete nat gateway and wait for deleted status.
  ec2_vpc_nat_gateway:
    state: absent
    nat_gateway_id: nat-12345678
    wait: yes
    wait_timeout: 500
    region: ap-southeast-2

- name: Delete nat gateway and release EIP.
  ec2_vpc_nat_gateway:
    state: absent
    nat_gateway_id: nat-12345678
    release_eip: yes
    wait: yes
    wait_timeout: 300
    region: ap-southeast-2
'''

RETURN = '''
create_time:
  description: The ISO 8601 date time formatin UTC.
  returned: In all cases.
  type: string
  sample: "2016-03-05T05:19:20.282000+00:00'"
nat_gateway_id:
  description: id of the VPC NAT Gateway
  returned: In all cases.
  type: string
  sample: "nat-0d1e3a878585988f8"
subnet_id:
  description: id of the Subnet
  returned: In all cases.
  type: string
  sample: "subnet-12345"
state:
  description: The current state of the NAT Gateway.
  returned: In all cases.
  type: string
  sample: "available"
vpc_id:
  description: id of the VPC.
  returned: In all cases.
  type: string
  sample: "vpc-12345"
nat_gateway_addresses:
  description: List of dictionairies containing the public_ip, network_interface_id, private_ip, and allocation_id.
  returned: In all cases.
  type: string
  sample: [
      {
          'public_ip': '52.52.52.52',
          'network_interface_id': 'eni-12345',
          'private_ip': '10.0.0.100',
          'allocation_id': 'eipalloc-12345'
      }
  ]
'''

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ec2 import ec2_argument_spec, get_aws_connection_info, boto3_conn

import datetime
import random
import re
import time

from dateutil.tz import tzutc

try:
    import botocore
    import boto3
    HAS_BOTO3 = True
except ImportError:
    HAS_BOTO3 = False

DRY_RUN_GATEWAYS = [
    {
        "nat_gateway_id": "nat-123456789",
        "subnet_id": "subnet-123456789",
        "nat_gateway_addresses": [
            {
                "public_ip": "55.55.55.55",
                "network_interface_id": "eni-1234567",
                "private_ip": "10.0.0.102",
                "allocation_id": "eipalloc-1234567"
            }
        ],
        "state": "available",
        "create_time": "2016-03-05T05:19:20.282000+00:00",
        "vpc_id": "vpc-12345678"
    }
]
DRY_RUN_GATEWAY_UNCONVERTED = [
    {
        'VpcId': 'vpc-12345678',
        'State': 'available',
        'NatGatewayId': 'nat-123456789',
        'SubnetId': 'subnet-123456789',
        'NatGatewayAddresses': [
            {
                'PublicIp': '55.55.55.55',
                'NetworkInterfaceId': 'eni-1234567',
                'AllocationId': 'eipalloc-1234567',
                'PrivateIp': '10.0.0.102'
            }
        ],
        'CreateTime': datetime.datetime(2016, 3, 5, 5, 19, 20, 282000, tzinfo=tzutc())
    }
]

DRY_RUN_ALLOCATION_UNCONVERTED = {
    'Addresses': [
        {
            'PublicIp': '55.55.55.55',
            'Domain': 'vpc',
            'AllocationId': 'eipalloc-1234567'
        }
    ]
}

DRY_RUN_MSGS = 'DryRun Mode:'


def convert_to_lower(data):
    """Convert all uppercase keys in dict with lowercase_

    Args:
        data (dict): Dictionary with keys that have upper cases in them
            Example.. FooBar == foo_bar
            if a val is of type datetime.datetime, it will be converted to
            the ISO 8601

    Basic Usage:
        >>> test = {'FooBar': []}
        >>> test = convert_to_lower(test)
        {
            'foo_bar': []
        }

    Returns:
        Dictionary
    """
    results = dict()
    if isinstance(data, dict):
        for key, val in data.items():
            key = re.sub(r'(([A-Z]{1,3}){1})', r'_\1', key).lower()
            if key[0] == '_':
                key = key[1:]
            if isinstance(val, datetime.datetime):
                results[key] = val.isoformat()
            elif isinstance(val, dict):
                results[key] = convert_to_lower(val)
            elif isinstance(val, list):
                converted = list()
                for item in val:
                    converted.append(convert_to_lower(item))
                results[key] = converted
            else:
                results[key] = val
    return results


def get_nat_gateways(client, subnet_id=None, nat_gateway_id=None,
                     states=None, check_mode=False):
    """Retrieve a list of NAT Gateways
    Args:
        client (botocore.client.EC2): Boto3 client

    Kwargs:
        subnet_id (str): The subnet_id the nat resides in.
        nat_gateway_id (str): The Amazon nat id.
        states (list): States available (pending, failed, available, deleting, and deleted)
            default=None

    Basic Usage:
        >>> client = boto3.client('ec2')
        >>> subnet_id = 'subnet-12345678'
        >>> get_nat_gateways(client, subnet_id)
        [
            true,
            "",
            {
                "nat_gateway_id": "nat-123456789",
                "subnet_id": "subnet-123456789",
                "nat_gateway_addresses": [
                    {
                        "public_ip": "55.55.55.55",
                        "network_interface_id": "eni-1234567",
                        "private_ip": "10.0.0.102",
                        "allocation_id": "eipalloc-1234567"
                    }
                ],
                "state": "deleted",
                "create_time": "2016-03-05T00:33:21.209000+00:00",
                "delete_time": "2016-03-05T00:36:37.329000+00:00",
                "vpc_id": "vpc-12345678"
            }

    Returns:
        Tuple (bool, str, list)
    """
    params = dict()
    err_msg = ""
    gateways_retrieved = False
    existing_gateways = list()
    if not states:
        states = ['available', 'pending']
    if nat_gateway_id:
        params['NatGatewayIds'] = [nat_gateway_id]
    else:
        params['Filter'] = [
            {
                'Name': 'subnet-id',
                'Values': [subnet_id]
            },
            {
                'Name': 'state',
                'Values': states
            }
        ]

    try:
        if not check_mode:
            gateways = client.describe_nat_gateways(**params)['NatGateways']
            if gateways:
                for gw in gateways:
                    existing_gateways.append(convert_to_lower(gw))
            gateways_retrieved = True
        else:
            gateways_retrieved = True
            if nat_gateway_id:
                if DRY_RUN_GATEWAYS[0]['nat_gateway_id'] == nat_gateway_id:
                    existing_gateways = DRY_RUN_GATEWAYS
            elif subnet_id:
                if DRY_RUN_GATEWAYS[0]['subnet_id'] == subnet_id:
                    existing_gateways = DRY_RUN_GATEWAYS
            err_msg = '{0} Retrieving gateways'.format(DRY_RUN_MSGS)

    except botocore.exceptions.ClientError as e:
        err_msg = str(e)

    return gateways_retrieved, err_msg, existing_gateways


def wait_for_status(client, wait_timeout, nat_gateway_id, status,
                    check_mode=False):
    """Wait for the NAT Gateway to reach a status
    Args:
        client (botocore.client.EC2): Boto3 client
        wait_timeout (int): Number of seconds to wait, until this timeout is reached.
        nat_gateway_id (str): The Amazon nat id.
        status (str): The status to wait for.
            examples. status=available, status=deleted

    Basic Usage:
        >>> client = boto3.client('ec2')
        >>> subnet_id = 'subnet-12345678'
        >>> allocation_id = 'eipalloc-12345678'
        >>> wait_for_status(client, subnet_id, allocation_id)
        [
            true,
            "",
            {
                "nat_gateway_id": "nat-123456789",
                "subnet_id": "subnet-1234567",
                "nat_gateway_addresses": [
                    {
                        "public_ip": "55.55.55.55",
                        "network_interface_id": "eni-1234567",
                        "private_ip": "10.0.0.102",
                        "allocation_id": "eipalloc-12345678"
                    }
                ],
                "state": "deleted",
                "create_time": "2016-03-05T00:33:21.209000+00:00",
                "delete_time": "2016-03-05T00:36:37.329000+00:00",
                "vpc_id": "vpc-12345677"
            }
        ]

    Returns:
        Tuple (bool, str, dict)
    """
    polling_increment_secs = 5
    wait_timeout = time.time() + wait_timeout
    status_achieved = False
    nat_gateway = dict()
    states = ['pending', 'failed', 'available', 'deleting', 'deleted']
    err_msg = ""

    while wait_timeout > time.time():
        try:
            gws_retrieved, err_msg, nat_gateways = (
                get_nat_gateways(
                    client, nat_gateway_id=nat_gateway_id,
                    states=states, check_mode=check_mode
                )
            )
            if gws_retrieved and nat_gateways:
                nat_gateway = nat_gateways[0]
                if check_mode:
                    nat_gateway['state'] = status

                if nat_gateway.get('state') == status:
                    status_achieved = True
                    break

                elif nat_gateway.get('state') == 'failed':
                    err_msg = nat_gateway.get('failure_message')
                    break

                elif nat_gateway.get('state') == 'pending':
                    if 'failure_message' in nat_gateway:
                        err_msg = nat_gateway.get('failure_message')
                        status_achieved = False
                        break

            else:
                time.sleep(polling_increment_secs)

        except botocore.exceptions.ClientError as e:
            err_msg = str(e)

    if not status_achieved:
        err_msg = "Wait time out reached, while waiting for results"

    return status_achieved, err_msg, nat_gateway


def gateway_in_subnet_exists(client, subnet_id, allocation_id=None,
                             check_mode=False):
    """Retrieve all NAT Gateways for a subnet.
    Args:
        subnet_id (str): The subnet_id the nat resides in.

    Kwargs:
        allocation_id (str): The EIP Amazon identifier.
            default = None

    Basic Usage:
        >>> client = boto3.client('ec2')
        >>> subnet_id = 'subnet-1234567'
        >>> allocation_id = 'eipalloc-1234567'
        >>> gateway_in_subnet_exists(client, subnet_id, allocation_id)
        (
            [
                {
                    "nat_gateway_id": "nat-123456789",
                    "subnet_id": "subnet-123456789",
                    "nat_gateway_addresses": [
                        {
                            "public_ip": "55.55.55.55",
                            "network_interface_id": "eni-1234567",
                            "private_ip": "10.0.0.102",
                            "allocation_id": "eipalloc-1234567"
                        }
                    ],
                    "state": "deleted",
                    "create_time": "2016-03-05T00:33:21.209000+00:00",
                    "delete_time": "2016-03-05T00:36:37.329000+00:00",
                    "vpc_id": "vpc-1234567"
                }
            ],
            False
        )

    Returns:
        Tuple (list, bool)
    """
    allocation_id_exists = False
    gateways = []
    states = ['available', 'pending']
    gws_retrieved, _, gws = (
        get_nat_gateways(
            client, subnet_id, states=states, check_mode=check_mode
        )
    )
    if not gws_retrieved:
        return gateways, allocation_id_exists
    for gw in gws:
        for address in gw['nat_gateway_addresses']:
            if allocation_id:
                if address.get('allocation_id') == allocation_id:
                    allocation_id_exists = True
                    gateways.append(gw)
            else:
                gateways.append(gw)

    return gateways, allocation_id_exists


def get_eip_allocation_id_by_address(client, eip_address, check_mode=False):
    """Release an EIP from your EIP Pool
    Args:
        client (botocore.client.EC2): Boto3 client
        eip_address (str): The Elastic IP Address of the EIP.

    Kwargs:
        check_mode (bool): if set to true, do not run anything and
            falsify the results.

    Basic Usage:
        >>> client = boto3.client('ec2')
        >>> eip_address = '52.87.29.36'
        >>> get_eip_allocation_id_by_address(client, eip_address)
        'eipalloc-36014da3'

    Returns:
        Tuple (str, str)
    """
    params = {
        'PublicIps': [eip_address],
    }
    allocation_id = None
    err_msg = ""
    try:
        if not check_mode:
            allocations = client.describe_addresses(**params)['Addresses']
            if len(allocations) == 1:
                allocation = allocations[0]
            else:
                allocation = None
        else:
            dry_run_eip = (
                DRY_RUN_ALLOCATION_UNCONVERTED['Addresses'][0]['PublicIp']
            )
            if dry_run_eip == eip_address:
                allocation = DRY_RUN_ALLOCATION_UNCONVERTED['Addresses'][0]
            else:
                allocation = None
        if allocation:
            if allocation.get('Domain') != 'vpc':
                err_msg = (
                    "EIP {0} is a non-VPC EIP, please allocate a VPC scoped EIP"
                    .format(eip_address)
                )
            else:
                allocation_id = allocation.get('AllocationId')
        else:
            err_msg = (
                "EIP {0} does not exist".format(eip_address)
            )

    except botocore.exceptions.ClientError as e:
        err_msg = str(e)

    return allocation_id, err_msg


def allocate_eip_address(client, check_mode=False):
    """Release an EIP from your EIP Pool
    Args:
        client (botocore.client.EC2): Boto3 client

    Kwargs:
        check_mode (bool): if set to true, do not run anything and
            falsify the results.

    Basic Usage:
        >>> client = boto3.client('ec2')
        >>> allocate_eip_address(client)
        True

    Returns:
        Tuple (bool, str)
    """
    ip_allocated = False
    new_eip = None
    err_msg = ''
    params = {
        'Domain': 'vpc',
    }
    try:
        if check_mode:
            ip_allocated = True
            random_numbers = (
                ''.join(str(x) for x in random.sample(range(0, 9), 7))
            )
            new_eip = 'eipalloc-{0}'.format(random_numbers)
        else:
            new_eip = client.allocate_address(**params)['AllocationId']
            ip_allocated = True
        err_msg = 'eipalloc id {0} created'.format(new_eip)

    except botocore.exceptions.ClientError as e:
        err_msg = str(e)

    return ip_allocated, err_msg, new_eip


def release_address(client, allocation_id, check_mode=False):
    """Release an EIP from your EIP Pool
    Args:
        client (botocore.client.EC2): Boto3 client
        allocation_id (str): The eip Amazon identifier.

    Kwargs:
        check_mode (bool): if set to true, do not run anything and
            falsify the results.

    Basic Usage:
        >>> client = boto3.client('ec2')
        >>> allocation_id = "eipalloc-123456"
        >>> release_address(client, allocation_id)
        True

    Returns:
        Boolean, string
    """
    err_msg = ''
    if check_mode:
        return True, ''

    ip_released = False
    try:
        client.describe_addresses(AllocationIds=[allocation_id])
    except botocore.exceptions.ClientError as e:
        # IP address likely already released
        # Happens with gateway in 'deleted' state that
        # still lists associations
        return True, str(e)
    try:
        client.release_address(AllocationId=allocation_id)
        ip_released = True
    except botocore.exceptions.ClientError as e:
        err_msg = str(e)

    return ip_released, err_msg


def create(client, subnet_id, allocation_id, client_token=None,
           wait=False, wait_timeout=0, if_exist_do_not_create=False,
           check_mode=False):
    """Create an Amazon NAT Gateway.
    Args:
        client (botocore.client.EC2): Boto3 client
        subnet_id (str): The subnet_id the nat resides in.
        allocation_id (str): The eip Amazon identifier.

    Kwargs:
        if_exist_do_not_create (bool): if a nat gateway already exists in this
            subnet, than do not create another one.
            default = False
        wait (bool): Wait for the nat to be in the deleted state before returning.
            default = False
        wait_timeout (int): Number of seconds to wait, until this timeout is reached.
            default = 0
        client_token (str):
            default = None

    Basic Usage:
        >>> client = boto3.client('ec2')
        >>> subnet_id = 'subnet-1234567'
        >>> allocation_id = 'eipalloc-1234567'
        >>> create(client, subnet_id, allocation_id, if_exist_do_not_create=True, wait=True, wait_timeout=500)
        [
            true,
            "",
            {
                "nat_gateway_id": "nat-123456789",
                "subnet_id": "subnet-1234567",
                "nat_gateway_addresses": [
                    {
                        "public_ip": "55.55.55.55",
                        "network_interface_id": "eni-1234567",
                        "private_ip": "10.0.0.102",
                        "allocation_id": "eipalloc-1234567"
                    }
                ],
                "state": "deleted",
                "create_time": "2016-03-05T00:33:21.209000+00:00",
                "delete_time": "2016-03-05T00:36:37.329000+00:00",
                "vpc_id": "vpc-1234567"
            }
        ]

    Returns:
        Tuple (bool, str, list)
    """
    params = {
        'SubnetId': subnet_id,
        'AllocationId': allocation_id
    }
    request_time = datetime.datetime.utcnow()
    changed = False
    success = False
    token_provided = False
    err_msg = ""

    if client_token:
        token_provided = True
        params['ClientToken'] = client_token

    try:
        if not check_mode:
            result = client.create_nat_gateway(**params)["NatGateway"]
        else:
            result = DRY_RUN_GATEWAY_UNCONVERTED[0]
            result['CreateTime'] = datetime.datetime.utcnow()
            result['NatGatewayAddresses'][0]['AllocationId'] = allocation_id
            result['SubnetId'] = subnet_id

        success = True
        changed = True
        create_time = result['CreateTime'].replace(tzinfo=None)
        if token_provided and (request_time > create_time):
            changed = False
        elif wait:
            success, err_msg, result = (
                wait_for_status(
                    client, wait_timeout, result['NatGatewayId'], 'available',
                    check_mode=check_mode
                )
            )
            if success:
                err_msg = (
                    'NAT gateway {0} created'.format(result['nat_gateway_id'])
                )

    except botocore.exceptions.ClientError as e:
        if "IdempotentParameterMismatch" in e.message:
            err_msg = (
                'NAT Gateway does not support update and token has already been provided'
            )
        else:
            err_msg = str(e)
            success = False
            changed = False
            result = None

    return success, changed, err_msg, result


def pre_create(client, subnet_id, allocation_id=None, eip_address=None,
               if_exist_do_not_create=False, wait=False, wait_timeout=0,
               client_token=None, check_mode=False):
    """Create an Amazon NAT Gateway.
    Args:
        client (botocore.client.EC2): Boto3 client
        subnet_id (str): The subnet_id the nat resides in.

    Kwargs:
        allocation_id (str): The EIP Amazon identifier.
            default = None
        eip_address (str): The Elastic IP Address of the EIP.
            default = None
        if_exist_do_not_create (bool): if a nat gateway already exists in this
            subnet, than do not create another one.
            default = False
        wait (bool): Wait for the nat to be in the deleted state before returning.
            default = False
        wait_timeout (int): Number of seconds to wait, until this timeout is reached.
            default = 0
        client_token (str):
            default = None

    Basic Usage:
        >>> client = boto3.client('ec2')
        >>> subnet_id = 'subnet-w4t12897'
        >>> allocation_id = 'eipalloc-36014da3'
        >>> pre_create(client, subnet_id, allocation_id, if_exist_do_not_create=True, wait=True, wait_timeout=500)
        [
            true,
            "",
            {
                "nat_gateway_id": "nat-03835afb6e31df79b",
                "subnet_id": "subnet-w4t12897",
                "nat_gateway_addresses": [
                    {
                        "public_ip": "52.87.29.36",
                        "network_interface_id": "eni-5579742d",
                        "private_ip": "10.0.0.102",
                        "allocation_id": "eipalloc-36014da3"
                    }
                ],
                "state": "deleted",
                "create_time": "2016-03-05T00:33:21.209000+00:00",
                "delete_time": "2016-03-05T00:36:37.329000+00:00",
                "vpc_id": "vpc-w68571b5"
            }
        ]

    Returns:
        Tuple (bool, bool, str, list)
    """
    success = False
    changed = False
    err_msg = ""
    results = list()

    if not allocation_id and not eip_address:
        existing_gateways, allocation_id_exists = (
            gateway_in_subnet_exists(client, subnet_id, check_mode=check_mode)
        )

        if len(existing_gateways) > 0 and if_exist_do_not_create:
            success = True
            changed = False
            results = existing_gateways[0]
            err_msg = (
                'NAT Gateway {0} already exists in subnet_id {1}'
                .format(
                    existing_gateways[0]['nat_gateway_id'], subnet_id
                )
            )
            return success, changed, err_msg, results
        else:
            success, err_msg, allocation_id = (
                allocate_eip_address(client, check_mode=check_mode)
            )
            if not success:
                return success, 'False', err_msg, dict()

    elif eip_address or allocation_id:
        if eip_address and not allocation_id:
            allocation_id, err_msg = (
                get_eip_allocation_id_by_address(
                    client, eip_address, check_mode=check_mode
                )
            )
            if not allocation_id:
                success = False
                changed = False
                return success, changed, err_msg, dict()

        existing_gateways, allocation_id_exists = (
            gateway_in_subnet_exists(
                client, subnet_id, allocation_id, check_mode=check_mode
            )
        )
        if len(existing_gateways) > 0 and (allocation_id_exists or if_exist_do_not_create):
            success = True
            changed = False
            results = existing_gateways[0]
            err_msg = (
                'NAT Gateway {0} already exists in subnet_id {1}'
                .format(
                    existing_gateways[0]['nat_gateway_id'], subnet_id
                )
            )
            return success, changed, err_msg, results

    success, changed, err_msg, results = create(
        client, subnet_id, allocation_id, client_token,
        wait, wait_timeout, if_exist_do_not_create, check_mode=check_mode
    )

    return success, changed, err_msg, results


def remove(client, nat_gateway_id, wait=False, wait_timeout=0,
           release_eip=False, check_mode=False):
    """Delete an Amazon NAT Gateway.
    Args:
        client (botocore.client.EC2): Boto3 client
        nat_gateway_id (str): The Amazon nat id.

    Kwargs:
        wait (bool): Wait for the nat to be in the deleted state before returning.
        wait_timeout (int): Number of seconds to wait, until this timeout is reached.
        release_eip (bool): Once the nat has been deleted, you can deallocate the eip from the vpc.

    Basic Usage:
        >>> client = boto3.client('ec2')
        >>> nat_gw_id = 'nat-03835afb6e31df79b'
        >>> remove(client, nat_gw_id, wait=True, wait_timeout=500, release_eip=True)
        [
            true,
            "",
            {
                "nat_gateway_id": "nat-03835afb6e31df79b",
                "subnet_id": "subnet-w4t12897",
                "nat_gateway_addresses": [
                    {
                        "public_ip": "52.87.29.36",
                        "network_interface_id": "eni-5579742d",
                        "private_ip": "10.0.0.102",
                        "allocation_id": "eipalloc-36014da3"
                    }
                ],
                "state": "deleted",
                "create_time": "2016-03-05T00:33:21.209000+00:00",
                "delete_time": "2016-03-05T00:36:37.329000+00:00",
                "vpc_id": "vpc-w68571b5"
            }
        ]

    Returns:
        Tuple (bool, str, list)
    """
    params = {
        'NatGatewayId': nat_gateway_id
    }
    success = False
    changed = False
    err_msg = ""
    results = list()
    states = ['pending', 'available' ]
    try:
        exist, _, gw = (
            get_nat_gateways(
                client, nat_gateway_id=nat_gateway_id,
                states=states, check_mode=check_mode
            )
        )
        if exist and len(gw) == 1:
            results = gw[0]
            if not check_mode:
                client.delete_nat_gateway(**params)

            allocation_id = (
                results['nat_gateway_addresses'][0]['allocation_id']
            )
            changed = True
            success = True
            err_msg = (
                'NAT gateway {0} is in a deleting state. Delete was successful'
                .format(nat_gateway_id)
            )

            if wait:
                status_achieved, err_msg, results = (
                    wait_for_status(
                        client, wait_timeout, nat_gateway_id, 'deleted',
                        check_mode=check_mode
                    )
                )
                if status_achieved:
                    err_msg = (
                        'NAT gateway {0} was deleted successfully'
                        .format(nat_gateway_id)
                    )

    except botocore.exceptions.ClientError as e:
        err_msg = str(e)

    if release_eip:
        eip_released, eip_err = (
            release_address(client, allocation_id, check_mode)
        )
        if not eip_released:
            err_msg = (
                "{0}: Failed to release EIP {1}: {2}"
                .format(err_msg, allocation_id, eip_err)
            )
            success = False

    return success, changed, err_msg, results


def main():
    argument_spec = ec2_argument_spec()
    argument_spec.update(
        dict(
            subnet_id=dict(type='str'),
            eip_address=dict(type='str'),
            allocation_id=dict(type='str'),
            if_exist_do_not_create=dict(type='bool', default=False),
            state=dict(default='present', choices=['present', 'absent']),
            wait=dict(type='bool', default=False),
            wait_timeout=dict(type='int', default=320, required=False),
            release_eip=dict(type='bool', default=False),
            nat_gateway_id=dict(type='str'),
            client_token=dict(type='str'),
        )
    )
    module = AnsibleModule(
        argument_spec=argument_spec,
        supports_check_mode=True,
        mutually_exclusive=[
            ['allocation_id', 'eip_address']
        ]
    )

    # Validate Requirements
    if not HAS_BOTO3:
        module.fail_json(msg='botocore/boto3 is required.')

    state = module.params.get('state').lower()
    check_mode = module.check_mode
    subnet_id = module.params.get('subnet_id')
    allocation_id = module.params.get('allocation_id')
    eip_address = module.params.get('eip_address')
    nat_gateway_id = module.params.get('nat_gateway_id')
    wait = module.params.get('wait')
    wait_timeout = module.params.get('wait_timeout')
    release_eip = module.params.get('release_eip')
    client_token = module.params.get('client_token')
    if_exist_do_not_create = module.params.get('if_exist_do_not_create')

    try:
        region, ec2_url, aws_connect_kwargs = (
            get_aws_connection_info(module, boto3=True)
        )
        client = (
            boto3_conn(
                module, conn_type='client', resource='ec2',
                region=region, endpoint=ec2_url, **aws_connect_kwargs
            )
        )
    except botocore.exceptions.ClientError as e:
        module.fail_json(msg="Boto3 Client Error - " + str(e.msg))

    changed = False
    err_msg = ''

    if state == 'present':
        if not subnet_id:
            module.fail_json(msg='subnet_id is required for creation')

        success, changed, err_msg, results = (
            pre_create(
                client, subnet_id, allocation_id, eip_address,
                if_exist_do_not_create, wait, wait_timeout,
                client_token, check_mode=check_mode
            )
        )
    else:
        if not nat_gateway_id:
            module.fail_json(msg='nat_gateway_id is required for removal')

        else:
            success, changed, err_msg, results = (
                remove(
                    client, nat_gateway_id, wait, wait_timeout, release_eip,
                    check_mode=check_mode
                )
            )

    if not success:
        module.fail_json(
            msg=err_msg, success=success, changed=changed
        )
    else:
        module.exit_json(
            msg=err_msg, success=success, changed=changed, **results
        )


if __name__ == '__main__':
    main()
