# Copyright The Cloud Custodian Authors.
# SPDX-License-Identifier: Apache-2.0
import base64
from datetime import datetime, timedelta
import functools
import json
import os
import time
import yaml

import jinja2
import jmespath
from dateutil import parser
from dateutil.tz import gettz, tzutc

try:
    from botocore.exceptions import ClientError
except ImportError:  # pragma: no cover
    pass  # Azure provider


class Providers:
    AWS = 0
    Azure = 1


def get_jinja_env(template_folders):
    env = jinja2.Environment(trim_blocks=True, autoescape=False)  # nosec nosemgrep
    env.filters['yaml_safe'] = functools.partial(yaml.safe_dump, default_flow_style=False)
    env.filters['date_time_format'] = date_time_format
    env.filters['get_date_time_delta'] = get_date_time_delta
    env.filters['from_json'] = json.loads
    env.filters['get_date_age'] = get_date_age
    env.globals['format_resource'] = resource_format
    env.globals['format_struct'] = format_struct
    env.globals['resource_tag'] = get_resource_tag_value
    env.globals['get_resource_tag_value'] = get_resource_tag_value
    env.globals['search'] = jmespath.search
    env.loader = jinja2.FileSystemLoader(template_folders)
    return env


def get_rendered_jinja(
        target, sqs_message, resources, logger,
        specified_template, default_template, template_folders):
    env = get_jinja_env(template_folders)
    mail_template = sqs_message['action'].get(specified_template, default_template)
    if not os.path.isabs(mail_template):
        mail_template = '%s.j2' % mail_template
    try:
        template = env.get_template(mail_template)
    except Exception as error_msg:
        logger.error("Invalid template reference %s\n%s" % (mail_template, error_msg))
        return

    # recast seconds since epoch as utc iso datestring, template
    # authors can use date_time_format helper func to convert local
    # tz. if no execution start time was passed use current time.
    execution_start = datetime.utcfromtimestamp(
        sqs_message.get(
            'execution_start',
            time.mktime(
                datetime.utcnow().timetuple())
        )).isoformat()

    rendered_jinja = template.render(
        recipient=target,
        resources=resources,
        account=sqs_message.get('account', ''),
        account_id=sqs_message.get('account_id', ''),
        partition=sqs_message.get('partition', ''),
        event=sqs_message.get('event', None),
        action=sqs_message['action'],
        policy=sqs_message['policy'],
        execution_start=execution_start,
        region=sqs_message.get('region', ''))
    return rendered_jinja


# eg, target_tag_keys could be resource-owners ['Owners', 'SupportTeam']
# and this function would go through the resource and look for any tag keys
# that match Owners or SupportTeam, and return those values as targets
def get_resource_tag_targets(resource, target_tag_keys):
    if 'Tags' not in resource:
        return []
    if isinstance(resource['Tags'], dict):
        tags = resource['Tags']
    else:
        tags = {tag['Key']: tag['Value'] for tag in resource['Tags']}
    targets = []
    for target_tag_key in target_tag_keys:
        if target_tag_key in tags:
            targets.append(tags[target_tag_key])
    return targets


def get_message_subject(sqs_message):
    default_subject = 'Custodian notification - %s' % (sqs_message['policy']['name'])
    subject = sqs_message['action'].get('subject', default_subject)
    jinja_template = jinja2.Template(subject)
    subject = jinja_template.render(
        account=sqs_message.get('account', ''),
        account_id=sqs_message.get('account_id', ''),
        partition=sqs_message.get('partition', ''),
        event=sqs_message.get('event', None),
        action=sqs_message['action'],
        policy=sqs_message['policy'],
        region=sqs_message.get('region', '')
    )
    return subject


def setup_defaults(config):
    config.setdefault('region', 'us-east-1')
    config.setdefault('ses_region', config.get('region'))
    config.setdefault('memory', 1024)
    config.setdefault('runtime', 'python3.7')
    config.setdefault('timeout', 300)
    config.setdefault('subnets', None)
    config.setdefault('security_groups', None)
    config.setdefault('contact_tags', [])
    config.setdefault('ldap_uri', None)
    config.setdefault('ldap_bind_dn', None)
    config.setdefault('ldap_bind_user', None)
    config.setdefault('ldap_bind_password', None)
    config.setdefault('endpoint_url', None)
    config.setdefault('datadog_api_key', None)
    config.setdefault('slack_token', None)
    config.setdefault('slack_webhook', None)


def date_time_format(utc_str, tz_str='US/Eastern', format='%Y %b %d %H:%M %Z'):
    return parser.parse(utc_str).astimezone(gettz(tz_str)).strftime(format)


def get_date_time_delta(delta):
    return str(datetime.now().replace(tzinfo=gettz('UTC')) + timedelta(delta))


def get_date_age(date):
    return (datetime.now(tz=tzutc()) - parser.parse(date)).days


def format_struct(evt):
    return json.dumps(evt, indent=2, ensure_ascii=False)


def get_resource_tag_value(resource, k):
    for t in resource.get('Tags', []):
        if t['Key'] == k:
            return t['Value']
    return ''


def strip_prefix(value, prefix):
    if value.startswith(prefix):
        return value[len(prefix):]
    return value


def resource_format(resource, resource_type):
    



def get_provider(mailer_config):
    if mailer_config.get('queue_url', '').startswith('asq://'):
        return Providers.Azure

    return Providers.AWS


def kms_decrypt(config, logger, session, encrypted_field):
    if config.get(encrypted_field):
        try:
            kms = session.client('kms')
            return kms.decrypt(
                CiphertextBlob=base64.b64decode(config[encrypted_field]))[
                    'Plaintext'].decode('utf8')
        except (TypeError, base64.binascii.Error) as e:
            logger.warning(
                "Error: %s Unable to base64 decode %s, will assume plaintext." %
                (e, encrypted_field))
        except ClientError as e:
            if e.response['Error']['Code'] != 'InvalidCiphertextException':
                raise
            logger.warning(
                "Error: %s Unable to decrypt %s with kms, will assume plaintext." %
                (e, encrypted_field))
        return config[encrypted_field]
    else:
        logger.debug("No encrypted value to decrypt.")
        return None


def decrypt(config, logger, session, encrypted_field):
    if config.get(encrypted_field):
        provider = get_provider(config)
        if provider == Providers.Azure:
            from c7n_mailer.azure_mailer.utils import azure_decrypt
            return azure_decrypt(config, logger, session, encrypted_field)
        elif provider == Providers.AWS:
            return kms_decrypt(config, logger, session, encrypted_field)
        else:
            raise Exception("Unknown provider")
    else:
        logger.debug("No encrypted value to decrypt.")
        return None


# https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-event-reference-user-identity.html
def get_aws_username_from_event(logger, event):
    if event is None:
        return None
    identity = event.get('detail', {}).get('userIdentity', {})
    if not identity:
        logger.warning("Could not get recipient from event \n %s" % (
            format_struct(event)))
        return None
    if identity['type'] == 'AssumedRole':
        logger.debug(
            'In some cases there is no ldap uid is associated with AssumedRole: %s',
            identity['arn'])
        logger.debug(
            'We will try to assume that identity is in the AssumedRoleSessionName')
        user = identity['arn'].rsplit('/', 1)[-1]
        if user is None or user.startswith('i-') or user.startswith('awslambda'):
            return None
        if ':' in user:
            user = user.split(':', 1)[-1]
        return user
    if identity['type'] == 'IAMUser' or identity['type'] == 'WebIdentityUser':
        return identity['userName']
    if identity['type'] == 'Root':
        return None
    # this conditional is left here as a last resort, it should
    # be better documented with an example UserIdentity json
    if ':' in identity['principalId']:
        user_id = identity['principalId'].split(':', 1)[-1]
    else:
        user_id = identity['principalId']
    return user_id

if resource_type.startswith('aws.'):
        resource_type = strip_prefix(resource_type, 'aws.')
    if resource_type == 'ec2':
        tag_map = {t['Key']: t['Value'] for t in resource.get('Tags', ())}
        return "%s %s %s %s %s %s" % (
            resource['InstanceId'],
            resource.get('VpcId', 'NO VPC!'),
            resource['InstanceType'],
            resource.get('LaunchTime'),
            tag_map.get('Name', ''),
            resource.get('PrivateIpAddress'))
    elif resource_type == 'ami':
        return "%s %s %s" % (
            resource.get('Name'), resource['ImageId'], resource['CreationDate'])
    elif resource_type == 'sagemaker-notebook':
        return "%s" % (resource['NotebookInstanceName'])
    elif resource_type == 's3':
        return "%s" % (resource['Name'])
    elif resource_type == 'ebs':
        return "%s %s %s %s" % (
            resource['VolumeId'],
            resource['Size'],
            resource['State'],
            resource['CreateTime'])
    elif resource_type == 'rds':
        return "%s %s %s %s" % (
            resource['DBInstanceIdentifier'],
            "%s-%s" % (
                resource['Engine'], resource['EngineVersion']),
            resource['DBInstanceClass'],
            resource['AllocatedStorage'])
    elif resource_type == 'rds-cluster':
        return "%s %s %s" % (
            resource['DBClusterIdentifier'],
            "%s-%s" % (
                resource['Engine'], resource['EngineVersion']),
            resource['AllocatedStorage'])
    elif resource_type == 'asg':
        tag_map = {t['Key']: t['Value'] for t in resource.get('Tags', ())}
        return "%s %s %s" % (
            resource['AutoScalingGroupName'],
            tag_map.get('Name', ''),
            "instances: %d" % (len(resource.get('Instances', []))))
    elif resource_type == 'elb':
        tag_map = {t['Key']: t['Value'] for t in resource.get('Tags', ())}
        if 'ProhibitedPolicies' in resource:
            return "%s %s %s %s" % (
                resource['LoadBalancerName'],
                "instances: %d" % len(resource['Instances']),
                "zones: %d" % len(resource['AvailabilityZones']),
                "prohibited_policies: %s" % ','.join(
                    resource['ProhibitedPolicies']))
        return "%s %s %s" % (
            resource['LoadBalancerName'],
            "instances: %d" % len(resource['Instances']),
            "zones: %d" % len(resource['AvailabilityZones']))
    elif resource_type == 'redshift':
        return "%s %s %s" % (
            resource['ClusterIdentifier'],
            'nodes:%d' % len(resource['ClusterNodes']),
            'encrypted:%s' % resource['Encrypted'])
    elif resource_type == 'emr':
        return "%s status:%s" % (
            resource['Id'],
            resource['Status']['State'])
    elif resource_type == 'cfn':
        return "%s" % (
            resource['StackName'])
    elif resource_type == 'launch-config':
        return "%s" % (
            resource['LaunchConfigurationName'])
    elif resource_type == 'security-group':
        name = resource.get('GroupName', '')
        for t in resource.get('Tags', ()):
            if t['Key'] == 'Name':
                name = t['Value']
        return "%s %s %s inrules: %d outrules: %d" % (
            name,
            resource['GroupId'],
            resource.get('VpcId', 'na'),
            len(resource.get('IpPermissions', ())),
            len(resource.get('IpPermissionsEgress', ())))
    elif resource_type == 'log-group':
        if 'lastWrite' in resource:
            return "name: %s last_write: %s" % (
                resource['logGroupName'],
                resource['lastWrite'])
        return "name: %s" % (resource['logGroupName'])
    elif resource_type == 'cache-cluster':
        return "name: %s created: %s status: %s" % (
            resource['CacheClusterId'],
            resource['CacheClusterCreateTime'],
            resource['CacheClusterStatus'])
    elif resource_type == 'cache-snapshot':
        cid = resource.get('CacheClusterId')
        if cid is None:
            cid = ', '.join([
                ns['CacheClusterId'] for ns in resource['NodeSnapshots']])
        return "name: %s cluster: %s source: %s" % (
            resource['SnapshotName'],
            cid,
            resource['SnapshotSource'])
    elif resource_type == 'redshift-snapshot':
        return "name: %s db: %s" % (
            resource['SnapshotIdentifier'],
            resource['DBName'])
    elif resource_type == 'ebs-snapshot':
        return "name: %s date: %s" % (
            resource['SnapshotId'],
            resource['StartTime'])
    elif resource_type == 'subnet':
        return "%s %s %s %s %s %s" % (
            resource['SubnetId'],
            resource['VpcId'],
            resource['AvailabilityZone'],
            resource['State'],
            resource['CidrBlock'],
            resource['AvailableIpAddressCount'])
    elif resource_type == 'account':
        return " %s %s" % (
            resource['account_id'],
            resource['account_name'])
    elif resource_type == 'cloudtrail':
        return "%s" % (
            resource['Name'])
    elif resource_type == 'vpc':
        return "%s " % (
            resource['VpcId'])
    elif resource_type == 'iam-group':
        return " %s %s %s" % (
            resource['GroupName'],
            resource['Arn'],
            resource['CreateDate'])
    elif resource_type == 'rds-snapshot':
        return " %s %s %s" % (
            resource['DBSnapshotIdentifier'],
            resource['DBInstanceIdentifier'],
            resource['SnapshotCreateTime'])
    elif resource_type == 'iam-user':
        return " %s " % (
            resource['UserName'])
    elif resource_type == 'iam-role':
        return " %s %s " % (
            resource['RoleName'],
            resource['CreateDate'])
    elif resource_type == 'iam-policy':
        return " %s " % (
            resource['PolicyName'])
    elif resource_type == 'iam-profile':
        return " %s " % (
            resource['InstanceProfileId'])
    elif resource_type == 'dynamodb-table':
        return "name: %s created: %s status: %s" % (
            resource['TableName'],
            resource['CreationDateTime'],
            resource['TableStatus'])
    elif resource_type == "sqs":
        return "QueueURL: %s QueueArn: %s " % (
            resource['QueueUrl'],
            resource['QueueArn'])
    elif resource_type == "efs":
        return "name: %s  id: %s  state: %s" % (
            resource['Name'],
            resource['FileSystemId'],
            resource['LifeCycleState']
        )
    elif resource_type == "network-addr":
        return "ip: %s  id: %s  scope: %s" % (
            resource['PublicIp'],
            resource['AllocationId'],
            resource['Domain']
        )
    elif resource_type == "route-table":
        return "id: %s  vpc: %s" % (
            resource['RouteTableId'],
            resource['VpcId']
        )
    elif resource_type == "app-elb":
        return "arn: %s  zones: %s  scheme: %s" % (
            resource['LoadBalancerArn'],
            len(resource['AvailabilityZones']),
            resource['Scheme'])
    elif resource_type == "nat-gateway":
        return "id: %s  state: %s  vpc: %s" % (
            resource['NatGatewayId'],
            resource['State'],
            resource['VpcId'])
    elif resource_type == "internet-gateway":
        return "id: %s  attachments: %s" % (
            resource['InternetGatewayId'],
            len(resource['Attachments']))
    elif resource_type == 'lambda':
        return "Name: %s  RunTime: %s  \n" % (
            resource['FunctionName'],
            resource['Runtime'])
    else:
        return "%s" % format_struct(resource)