#!/usr/bin/python
# Copyright: Ansible Project
# 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


DOCUMENTATION = '''
---
module: ec2_vpc_igw
version_added: 1.0.0
short_description: Manage an AWS VPC Internet gateway
description:
    - Manage an AWS VPC Internet gateway
author: Robert Estelle (@erydo)
options:
  vpc_id:
    description:
      - The VPC ID for the VPC in which to manage the Internet Gateway.
    required: true
    type: str
  tags:
    description:
      - "A dict of tags to apply to the internet gateway. Any tags currently applied to the internet gateway and not present here will be removed."
    aliases: [ 'resource_tags' ]
    type: dict
  state:
    description:
      - Create or terminate the IGW
    default: present
    choices: [ 'present', 'absent' ]
    type: str
extends_documentation_fragment:
- amazon.aws.aws
- amazon.aws.ec2

requirements:
  - botocore
  - boto3
'''

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

# Ensure that the VPC has an Internet Gateway.
# The Internet Gateway ID is can be accessed via {{igw.gateway_id}} for use in setting up NATs etc.
- community.aws.ec2_vpc_igw:
    vpc_id: vpc-abcdefgh
    state: present
  register: igw

'''

RETURN = '''
changed:
  description: If any changes have been made to the Internet Gateway.
  type: bool
  returned: always
  sample:
    changed: false
gateway_id:
  description: The unique identifier for the Internet Gateway.
  type: str
  returned: I(state=present)
  sample:
    gateway_id: "igw-XXXXXXXX"
tags:
  description: The tags associated the Internet Gateway.
  type: dict
  returned: I(state=present)
  sample:
    tags:
      "Ansible": "Test"
vpc_id:
  description: The VPC ID associated with the Internet Gateway.
  type: str
  returned: I(state=present)
  sample:
    vpc_id: "vpc-XXXXXXXX"
'''

try:
    import botocore
except ImportError:
    pass  # caught by AnsibleAWSModule

from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
from ansible_collections.amazon.aws.plugins.module_utils.waiters import get_waiter
from ansible_collections.amazon.aws.plugins.module_utils.ec2 import (
    AWSRetry,
    camel_dict_to_snake_dict,
    boto3_tag_list_to_ansible_dict,
    ansible_dict_to_boto3_filter_list,
    ansible_dict_to_boto3_tag_list,
    compare_aws_tags
)
from ansible.module_utils.six import string_types


class AnsibleEc2Igw(object):

    def __init__(self, module, results):
        self._module = module
        self._results = results
        self._connection = self._module.client('ec2')
        self._check_mode = self._module.check_mode

    def process(self):
        vpc_id = self._module.params.get('vpc_id')
        state = self._module.params.get('state', 'present')
        tags = self._module.params.get('tags')

        if state == 'present':
            self.ensure_igw_present(vpc_id, tags)
        elif state == 'absent':
            self.ensure_igw_absent(vpc_id)

    def get_matching_igw(self, vpc_id):
        filters = ansible_dict_to_boto3_filter_list({'attachment.vpc-id': vpc_id})
        igws = []
        try:
            response = self._connection.describe_internet_gateways(Filters=filters)
            igws = response.get('InternetGateways', [])
        except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
            self._module.fail_json_aws(e)

        igw = None
        if len(igws) > 1:
            self._module.fail_json(
                msg='EC2 returned more than one Internet Gateway for VPC {0}, aborting'.format(vpc_id))
        elif igws:
            igw = camel_dict_to_snake_dict(igws[0])

        return igw

    def check_input_tags(self, tags):
        nonstring_tags = [k for k, v in tags.items() if not isinstance(v, string_types)]
        if nonstring_tags:
            self._module.fail_json(msg='One or more tags contain non-string values: {0}'.format(nonstring_tags))

    def ensure_tags(self, igw_id, tags, add_only):
        final_tags = []

        filters = ansible_dict_to_boto3_filter_list({'resource-id': igw_id, 'resource-type': 'internet-gateway'})
        cur_tags = None
        try:
            cur_tags = self._connection.describe_tags(Filters=filters)
        except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
            self._module.fail_json_aws(e, msg="Couldn't describe tags")

        purge_tags = bool(not add_only)
        to_update, to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(cur_tags.get('Tags')), tags, purge_tags)
        final_tags = boto3_tag_list_to_ansible_dict(cur_tags.get('Tags'))

        if to_update:
            try:
                if self._check_mode:
                    # update tags
                    final_tags.update(to_update)
                else:
                    AWSRetry.exponential_backoff()(self._connection.create_tags)(
                        Resources=[igw_id],
                        Tags=ansible_dict_to_boto3_tag_list(to_update)
                    )

                self._results['changed'] = True
            except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
                self._module.fail_json_aws(e, msg="Couldn't create tags")

        if to_delete:
            try:
                if self._check_mode:
                    # update tags
                    for key in to_delete:
                        del final_tags[key]
                else:
                    tags_list = []
                    for key in to_delete:
                        tags_list.append({'Key': key})

                    AWSRetry.exponential_backoff()(self._connection.delete_tags)(Resources=[igw_id], Tags=tags_list)

                self._results['changed'] = True
            except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
                self._module.fail_json_aws(e, msg="Couldn't delete tags")

        if not self._check_mode and (to_update or to_delete):
            try:
                response = self._connection.describe_tags(Filters=filters)
                final_tags = boto3_tag_list_to_ansible_dict(response.get('Tags'))
            except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
                self._module.fail_json_aws(e, msg="Couldn't describe tags")

        return final_tags

    @staticmethod
    def get_igw_info(igw):
        return {
            'gateway_id': igw['internet_gateway_id'],
            'tags': igw['tags'],
            'vpc_id': igw['vpc_id']
        }

    def ensure_igw_absent(self, vpc_id):
        igw = self.get_matching_igw(vpc_id)
        if igw is None:
            return self._results

        if self._check_mode:
            self._results['changed'] = True
            return self._results

        try:
            self._results['changed'] = True
            self._connection.detach_internet_gateway(InternetGatewayId=igw['internet_gateway_id'], VpcId=vpc_id)
            self._connection.delete_internet_gateway(InternetGatewayId=igw['internet_gateway_id'])
        except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
            self._module.fail_json_aws(e, msg="Unable to delete Internet Gateway")

        return self._results

    def ensure_igw_present(self, vpc_id, tags):
        self.check_input_tags(tags)

        igw = self.get_matching_igw(vpc_id)

        if igw is None:
            if self._check_mode:
                self._results['changed'] = True
                self._results['gateway_id'] = None
                return self._results

            try:
                response = self._connection.create_internet_gateway()

                # Ensure the gateway exists before trying to attach it or add tags
                waiter = get_waiter(self._connection, 'internet_gateway_exists')
                waiter.wait(InternetGatewayIds=[response['InternetGateway']['InternetGatewayId']])

                igw = camel_dict_to_snake_dict(response['InternetGateway'])
                self._connection.attach_internet_gateway(InternetGatewayId=igw['internet_gateway_id'], VpcId=vpc_id)
                self._results['changed'] = True
            except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
                self._module.fail_json_aws(e, msg='Unable to create Internet Gateway')

        igw['vpc_id'] = vpc_id

        igw['tags'] = self.ensure_tags(igw_id=igw['internet_gateway_id'], tags=tags, add_only=False)

        igw_info = self.get_igw_info(igw)
        self._results.update(igw_info)

        return self._results


def main():
    argument_spec = dict(
        vpc_id=dict(required=True),
        state=dict(default='present', choices=['present', 'absent']),
        tags=dict(default=dict(), required=False, type='dict', aliases=['resource_tags'])
    )

    module = AnsibleAWSModule(
        argument_spec=argument_spec,
        supports_check_mode=True,
    )
    results = dict(
        changed=False
    )
    igw_manager = AnsibleEc2Igw(module=module, results=results)
    igw_manager.process()

    module.exit_json(**results)


if __name__ == '__main__':
    main()
