#     Copyright 2014 Netflix, Inc.
#
#     Licensed under the Apache License, Version 2.0 (the "License");
#     you may not use this file except in compliance with the License.
#     You may obtain a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#     Unless required by applicable law or agreed to in writing, software
#     distributed under the License is distributed on an "AS IS" BASIS,
#     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#     See the License for the specific language governing permissions and
#     limitations under the License.
"""
.. module: security_monkey.watchers.elb
    :platform: Unix

.. version:: $$VERSION$$
.. moduleauthor:: Patrick Kelley <pkelley@netflix.com> @monkeysecurity

"""
from security_monkey.watcher import Watcher
from security_monkey.watcher import ChangeItem
from security_monkey.constants import TROUBLE_REGIONS
from security_monkey.exceptions import BotoConnectionIssue
from security_monkey.datastore import Account
from security_monkey import app

from boto.ec2.elb import regions


def parse_policy(policy):
    ret = {}
    ret['name'] = policy['name']
    ret['type'] = policy['type']
    attrs = policy['Attributes']

    if policy['type'] != 'SSLNegotiationPolicyType':
        return ret

    ret['sslv2'] = bool(attrs.get('Protocol-SSLv2'))
    ret['sslv3'] = bool(attrs.get('Protocol-SSLv3'))
    ret['tlsv1'] = bool(attrs.get('Protocol-TLSv1'))
    ret['tlsv1_1'] = bool(attrs.get('Protocol-TLSv1.1'))
    ret['tlsv1_2'] = bool(attrs.get('Protocol-TLSv1.2'))
    ret['server_defined_cipher_order'] = bool(attrs.get('Server-Defined-Cipher-Order'))
    ret['reference_security_policy'] = attrs.get('Reference-Security-Policy', None)

    non_ciphers = [
        'Server-Defined-Cipher-Order',
        'Protocol-SSLv2',
        'Protocol-SSLv3',
        'Protocol-TLSv1',
        'Protocol-TLSv1.1',
        'Protocol-TLSv1.2',
        'Reference-Security-Policy'
    ]

    ciphers = []
    for cipher in attrs:
        if attrs[cipher] and cipher not in non_ciphers:
            ciphers.append(cipher)

    ciphers.sort()
    ret['supported_ciphers'] = ciphers

    return ret


class ELB(Watcher):
    index = 'elb'
    i_am_singular = 'ELB'
    i_am_plural = 'ELBs'

    def __init__(self, accounts=None, debug=False):
        super(ELB, self).__init__(accounts=accounts, debug=debug)

    def _setup_botocore(self, account):
        from security_monkey.common.sts_connect import connect
        self.botocore_session = connect(account, 'botocore')

    def _get_listener_policies(self, operation, elb):
        response_data = self.wrap_aws_rate_limited_call(operation, LoadBalancerName=elb.name)
        policies = {}
        for policy in response_data.get('PolicyDescriptions', []):
            p = {"name": policy['PolicyName'], "type": policy['PolicyTypeName'], "Attributes": {}}
            for attribute in policy['PolicyAttributeDescriptions']:
                if attribute['AttributeValue'] == "true":
                    p['Attributes'][attribute['AttributeName']] = True
                elif attribute['AttributeValue'] == "false":
                    p['Attributes'][attribute['AttributeName']] = False
                else:
                    p['Attributes'][attribute['AttributeName']] = attribute['AttributeValue']

            # This next bit may overwrite anything you did in the above for-loop:
            if "Reference-Security-Policy" in p['Attributes']:
                p['reference_security_policy'] = p['Attributes']['Reference-Security-Policy']
                del p['Attributes']
            else:
                p = parse_policy(p)

            policies[policy['PolicyName']] = p

        return policies

    def slurp(self):
        """
        :returns: item_list - list of ELB's.
        :returns: exception_map - A dict where the keys are a tuple containing the
            location of the exception and the value is the actual exception

        """
        self.prep_for_slurp()
        from security_monkey.common.sts_connect import connect
        item_list = []
        exception_map = {}
        for account in self.accounts:
            account_db = Account.query.filter(Account.name == account).first()
            account_number = account_db.number

            self._setup_botocore(account)
            for region in regions():
                app.logger.debug("Checking {}/{}/{}".format(self.index, account, region.name))
                elb_conn = connect(account, 'ec2.elb', region=region.name)

                botocore_client = self.botocore_session.create_client('elb', region_name=region.name)
                botocore_operation = botocore_client.describe_load_balancer_policies

                try:
                    all_elbs = []
                    marker = None

                    while True:
                        response = self.wrap_aws_rate_limited_call(
                            elb_conn.get_all_load_balancers,
                            marker=marker
                        )

                        # build our elb list
                        all_elbs.extend(response)

                        # ensure that we get every elb
                        if response.next_marker:
                            marker = response.next_marker
                        else:
                            break

                except Exception as e:
                    if region.name not in TROUBLE_REGIONS:
                        exc = BotoConnectionIssue(str(e), self.index, account, region.name)
                        self.slurp_exception((self.index, account, region.name), exc, exception_map,
                                             source="{}-watcher".format(self.index))
                    continue

                app.logger.debug("Found {} {}".format(len(all_elbs), self.i_am_plural))
                for elb in all_elbs:

                    if self.check_ignore_list(elb.name):
                        continue

                    try:
                        elb_map = {}
                        elb_map['availability_zones'] = list(elb.availability_zones)
                        elb_map['canonical_hosted_zone_name'] = elb.canonical_hosted_zone_name
                        elb_map['canonical_hosted_zone_name_id'] = elb.canonical_hosted_zone_name_id
                        elb_map['dns_name'] = elb.dns_name
                        elb_map['health_check'] = {'target': elb.health_check.target, 'interval': elb.health_check.interval}
                        elb_map['is_cross_zone_load_balancing'] = self.wrap_aws_rate_limited_call(
                            elb.is_cross_zone_load_balancing
                        )
                        elb_map['scheme'] = elb.scheme
                        elb_map['security_groups'] = list(elb.security_groups)
                        elb_map['source_security_group'] = elb.source_security_group.name
                        elb_map['subnets'] = list(elb.subnets)
                        elb_map['vpc_id'] = elb.vpc_id
                        elb_map['is_logging'] = self.wrap_aws_rate_limited_call(