# Copyright 2019-2020 Turner Broadcasting Inc. / WarnerMedia
#
# 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.

import boto3
from botocore.exceptions import ClientError
from boto3.dynamodb.conditions import Key
import json
import os
import logging
import datetime
from dateutil import tz
from pprint import pprint

from .aws_account import *
from .config import *

import logging
logger = logging.getLogger('antiope.VPC')

class VPC(object):
    """Class to represent a VPC belonging to an Account"""
    def __init__(self, vpc_id, account=None, config=None):
        '''create a new VPC Instance from the vpc_id from data in the VPC Table'''
        super(VPC, self).__init__()
        self.vpc_id = vpc_id

        if config is None:
            # Assume we're able to get what we need from EnvVars
            self.config = AntiopeConfig()
        else:
            self.config = config

        # # Save these as attributes
        self.account_table = self.config.account_table
        self.vpc_table     = self.config.vpc_table

        # Get the VPC data from the VPC DDB Table. Raise the VPCLookupError if not present
        try:
            response = self.vpc_table.query(
                KeyConditionExpression=Key('vpc_id').eq(self.vpc_id),
                Select='ALL_ATTRIBUTES'
            )
        except ClientError as e:
            raise VPCLookupError("ClientError getting {}: {}".format(self.vpc_id, e))

        try:
            self.db_record = response['Items'][0]

            # Make sure there is a name, if not then use the VPC ID
            if 'name' not in self.db_record:
                self.db_record['name'] = self.vpc_id

            # Convert the response into instance attributes
            self.__dict__.update(self.db_record)
        except IndexError as e:
            raise VPCLookupError("ID {} not found".format(vpc_id))
        except Exception as e:
            logger.error("Got Other error: {}".format(e))

        # We should reference our account in the VPC object. Perhaps the object exists and is part of the init(),
        # perhaps it isn't and we need to create a new one.
        if account is None:
            # This will fail because AWSAccount is not defined and can't be imported due to circular dependencies
            # (AWSAccount requires VPC too)
            self.account = AWSAccount(self.account_id)
        else:
            self.account = account

    def __str__(self):
        """when converted to a string, become the account_id"""
        return(self.vpc_id)

    def __repr__(self):
        '''Create a useful string for this class if referenced'''
        return("<VPC {} >".format(self.vpc_id))

    #
    # Database functions
    #
    def update_vpc_attribute(self, key, value):
        '''    update a specific attribute in a specific table for this account
            table_name should be a valid DynDB table, key is the column, value is the new value to set
        '''
        logger.info(u"Adding key:{} value:{} to VPC {}".format(key, value, self))
        try:
            response = self.vpc_table.update_item(
                Key= {
                    'vpc_id': self.vpc_id
                },
                UpdateExpression="set {} = :r".format(key),
                ExpressionAttributeValues={
                    ':r': value,
                }
            )
            setattr(self, key, value)  # Also update this instance of the object
        except ClientError as e:
            raise AccountUpdateError("Failed to update {} to {} for {}: {}".format(key, value, self, e))

    def get_vpc_attribute(self, key):
        '''
        Pulls a attribute from the specificed table for the account
        '''
        logger.info(u"Getting key: {} for {}".format(key, self))
        try:
            response = self.vpc_table.get_item(
                Key= {
                    'vpc_id': self.vpc_id
                },
                AttributesToGet=[key]
            )
            return(response['Item'][key])
        except ClientError as e:
            raise VPCLookupError("Failed to get {} for {}: {}".format(key, self, e))
        except KeyError as e:
            raise VPCLookupError("Failed to get {} for {}: {}".format(key, self, e))

    #
    # Instance Attributes
    #
    def query_instances(self, instance_state = None):
        '''return an array of dict representing the data from describe_instances()'''
        output = []

        filters = [{'Name': 'vpc-id', 'Values': [self.vpc_id]}]
        if instance_state is not None:
            filters.append({'Name': 'instance-state-name', 'Values': [instance_state]})

        try:
            # Get a boto3 EC2 Resource in this VPC's region.
            ec2 = self.account.get_client('ec2', self.region)
            response = ec2.describe_instances(
                Filters = filters,
                MaxResults = 1000
            )
            while 'NextToken' in response:
                for r in response['Reservations']:
                    for i in r['Instances']:
                        output.append(i)
                response = ec2.describe_instances(
                    Filters = filters,
                    MaxResults = 1000,
                    NextToken = response['NextToken']
                )
            # Done with the while loop (or never entered it) do the last batch
            for r in response['Reservations']:
                for i in r['Instances']:
                    output.append(i)
            return(output)
        except ClientError as e:
            raise VPCLookupError("Failed to query_instances() for {}: {}".format(self, e))

    def query_running_instances(self):
        '''return an array of dict representing the data from describe_instances(). Only includes running instances'''
        return(self.query_instances(instance_state = "running"))

    def discover_instance_count(self):
        '''Get the number of instances in the VPC, update the count in the database'''

        # FIXME (someday) - if the last_updated is within some timeperiod, then don't make the API calls, just used the cached info

        state_count = {
            "pending": 0,
            "running": 0,
            "shutting-down": 0,
            "terminated": 0,
            "stopping": 0,
            "stopped": 0
        }

        instances = self.query_instances()
        for i in instances:
            state = i['State']['Name']
            state_count[state] += 1

        item = {
            "last_updated": str(datetime.datetime.now(tz.gettz('US/Eastern'))),
            "states": state_count
        }

        self.update_vpc_attribute("instance_states", item)
        return(state_count)

    def is_active(self):
        '''Returns true if there are active EC2 Instances running in this VPC'''

        if not hasattr(self, "instance_states"):
            self.discover_instance_count()

        if self.instance_states['running'] > 0:  # FIXME, do something about the instance counts being stale
            return(True)
        return(False)

