# -*- coding: utf-8 -*-
# Copyright (c) 2017 CloudZero - ALL RIGHTS RESERVED - PROPRIETARY AND CONFIDENTIAL
# Unauthorized copying of this file and/or project, via any medium is strictly prohibited.
# Direct all questions to legal@cloudzero.com

"""
Contains functions to reliably extract cloud local IDs from AWS ARNs.
This code is pretty hairy and makes you appreciate just how crazy the AWS identifier world is.
To best understand this module, it's best to consult the unit tests which cover this thing exhaustively.
At least the ugly can be kept here and out of the API!
"""

from datetime import timezone

from dateutil.parser import parse

from czid.czrn.exceptions import ResourceWildcardError
from czid.czrn.internal.resource_ids import \
    get_resource_type_from_cloud_local_id


def get_cloud_local_id_from_arn_components(service, resource_details, resource_type):
    """
    Get the resource-specific cloud-local-id from from the components of an ARN.
    A cloud-local-id is not globally unique, but is guaranteed to be unique
    for a service-type/resource-type combination.
    Cloud-local-IDs may be a single string, but they may also be a multi-part
    identifier delimited by slashes.  In some cases, a service's local scoping rules
    require several bits of information to prevent collisions.

    Args:
        service (str):
        resource_details (str):
        resource_type (str): A resource type

    Returns:
        str: a cloud-local-id for the resource
    """
    if resource_type == 'service-usage':
        return resource_details.split('/')[1]
    elif service in _cloud_local_id_custom_handlers:
        return _cloud_local_id_custom_handlers[service](resource_details, resource_type)
    elif '/' in resource_details:
        return resource_details.split('/')[1]
    elif ':' in resource_details:
        return resource_details.split(':')[1]
    elif get_resource_type_from_cloud_local_id(resource_details):
        return resource_details
    else:
        return resource_details


def _extract_acm_pca_resource_types(arn_resource, resource_type):
    segments = arn_resource.split('/')
    if resource_type == 'certificate':
        return segments[3]
    return segments[1]


def _extract_apigateway_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if resource_type == 'authorizer':
        rest_api = parts[0]
        authorizer_id = parts[2]
        return '|'.join([rest_api, authorizer_id])

    # There are two known forms for rest-apis and stages; handle the weird one first
    segments = arn_resource.split('/')
    if len(segments) == 3 and segments[1].endswith('apis'):
        return segments[2]
    if len(segments) == 5 and segments[1].endswith('apis') and segments[3] == 'stages':
        return f'{segments[2]}|{segments[4]}'

    rest_api = parts[0]
    stage = parts[1]
    verb = parts[2]
    resource = '/' + '/'.join(parts[3:])
    return '|'.join([rest_api, stage, verb, resource])


def _extract_autoscaling_cloud_local_ids(arn_resource, resource_type):
    if resource_type == 'group':
        return arn_resource.split(':')[1]
    elif resource_type == 'policy':
        return arn_resource.split(':')[1] + '|' + arn_resource.split(':')[3].split('/')[1]
    elif resource_type == 'launch-configuration':
        return arn_resource.split(':')[2].split('/')[1]


def _extract_logs_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split(':')
    if resource_type == 'log-group':
        if '*' in parts[1]:
            raise ResourceWildcardError(f'Wildcards not supported in log group names {parts[1]}')
        return parts[1]
    elif resource_type == 'log-stream':
        if '*' in parts[3]:
            raise ResourceWildcardError(f'Wildcards not supported in log stream names {parts[3]}')
        return parts[1] + '|' + parts[3]
    elif resource_type == 'destination':
        return parts[1]


def _extract_sns_cloud_local_ids(arn_resource, resource_type):
    slash_parts = arn_resource.split('/')
    colon_parts = arn_resource.split(':')
    if resource_type in {'platform-application', 'platform-endpoint'}:
        return slash_parts[-1:][0]
    if len(colon_parts) == 2:
        return colon_parts[-1:][0]
    return arn_resource


def _extract_cloudformation_cloud_local_ids(arn_resource, resource_type):
    if resource_type == "stack":
        return arn_resource.split('/')[1]
    else:
        return arn_resource.split('/')[2]


def _extract_codebuild_cloud_local_ids(arn_resource, resource_type):
    if resource_type == 'project':
        return arn_resource.split('/')[1]
    elif resource_type == 'build':
        return arn_resource.split(':')[1]


def _extract_cognito_sync_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if resource_type == 'identity-pool':
        return parts[1]
    elif resource_type == 'identity':
        return parts[1] + '|' + parts[3]
    elif resource_type == 'dataset':
        return parts[1] + '|' + parts[3] + '|' + parts[5]


def _extract_glue_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if resource_type in {'database', 'connection', 'dev-endpoint', 'mltransform'}:
        return parts[1].lower()
    elif resource_type in {'table', 'user-defined-function'}:
        new_parts = parts[1] + '|' + parts[2]
        return new_parts
    elif resource_type == 'job':
        return parts[1]
    elif resource_type == 'root-catalog':
        return 'catalog'
    elif resource_type == 'crawler':
        return parts[1]


def _extract_datasync_cloud_local_ids(arn_resource, resource_type):
    segments = arn_resource.split('/')
    if len(segments) > 2:
        return segments[3]
    return segments[1]


def _extract_dynamodb_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if resource_type == 'table':
        return parts[1]
    elif resource_type == 'stream':
        tzinfos = {'UTC': timezone.utc}
        return parts[1] + '|' + str(int(parse(parts[3] + ' UTC', tzinfos=tzinfos).timestamp() * 1000), )
    elif resource_type == 'index' or resource_type == 'backup':
        return parts[1] + '|' + parts[3]


def _extract_ecs_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if len(parts) == 3:  # Support new ECS ARN format
        return f"{parts[1]}|{parts[2]}"
    else:
        return parts[1].replace(':', "|")


def _extract_ecr_public_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    return '/'.join(parts[1:])


def _extract_elasticbeanstalk_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if resource_type in {'application', 'solution-stack'}:
        return parts[1]
    elif resource_type in {'application-version', 'environment', 'configuration-template'}:
        return parts[1] + '|' + parts[2]


def _extract_elasticloadbalancing_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if resource_type in {'app-load-balancer', 'net-load-balancer', 'classic-load-balancer'}:
        if len(parts) == 4:
            return parts[-2]
        elif len(parts) < 4:
            return parts[-1]
    if resource_type in {'app-load-balancer-listener', 'app-load-balancer-listener-rule', 'net-load-balancer-listener',
                         'net-load-balancer-listener-rule', 'target-group'}:
        return arn_resource.split('/')[-1:][0]


def _extract_iam_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if resource_type == 'aws-account':
        return 'root'
    elif resource_type in {'service-role', 'aws-service-role'}:
        return parts[-1:][0]
    return '|'.join(parts[1:])


def _extract_organizations_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if resource_type == 'policy':
        return parts[1] + '|' + parts[3]
    else:
        return '|'.join(parts[1:])


def _extract_lambda_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split(':')
    if resource_type == 'layer-version':
        return parts[1] + '|' + parts[2]
    else:
        return '|'.join(arn_resource.split(':')[1:])


def _extract_kinesis_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if resource_type == 'stream-consumer':
        # stream/example-stream-name/consumer/example-consumer-name:timestamp
        # We want to return
        # example-stream-name/example-consumer-name|timestamp
        if len(parts) == 4:
            return parts[1] + '/' + parts[-1].replace(':', '|')

    return parts[1]


def _extract_kms_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    return '/'.join(parts[1:])


def _extract_macie_cloud_local_ids(arn_resource, resource_type):
    segments = arn_resource.split('/')
    if len(segments) > 2:
        return segments[3]
    return segments[1]


def _extract_rds_cloud_local_ids(arn_resource, resource_type):
    if len(arn_resource.split(':')) == 3:
        return arn_resource.split(':')[-1] + '|default'
    elif len(arn_resource.split(':')) == 2:
        return arn_resource.split(':')[-1]


def _extract_redshift_cloud_local_ids(arn_resource, resource_type):
    new_resource = arn_resource.split(':')[-1]
    if new_resource.split('.')[0] == 'default':
        return '.'.join(new_resource.split('.')[1:]) + '|default'
    else:
        return new_resource.split(':')[-1:][0].replace('/', '|')


def _extract_route53resolver_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if resource_type == 'bound-network-interface':
        return parts[0]
    return parts[1]


def _extract_secretsmanager_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split(':')
    return parts[1]


def _extract_s3_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if parts[0] == 'storage-lens':
        return parts[1]

    if len(parts) >= 2:
        return None
    return parts[0]


def _extract_serverlessrepo_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if resource_type == 'semantic-version':
        return parts[1] + '|' + parts[3]
    else:
        return parts[1]


def _extract_storagegateway_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if resource_type in {'gateway', 'share', 'tape'}:
        return parts[1]
    elif resource_type in {'scsi-target', 'volume', 'device'}:
        return parts[1] + '|' + parts[3]


def _extract_timestream_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if len(parts) >= 4:
        return parts[1] + '|' + parts[3]

    return parts[1]


def _extract_transfer_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if resource_type == 'user':
        return '/'.join(parts[1:])
    return parts[1]


def _extract_airflow_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if resource_type == 'role':
        return '/'.join(parts[1:])
    return parts[1]


def _extract_appstream_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if resource_type == 'user':
        return '/'.join(parts[1:])
    return parts[1]


def _extract_cassandra_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    if resource_type == 'keyspace':
        return '/'.join(parts[1:])
    return parts[1]


def _extract_wafv2_cloud_local_ids(arn_resource, resource_type):
    parts = arn_resource.split('/')
    return '/'.join(parts[1:])


"""
Define a mapping of service types to custom functions used for extracting cloud-local-IDs from an ARN.
A number of services (e.g. EC2) have a very regular, simple structure and can be processed without special code.
For example, consider the following two examples:
    - arn:aws:cloudtrail:us-east-1:123456789012:trail/mytrailname
    - arn:aws:medialive:us-east-1:111111111111:channel:3456789
In both of these cases, the ARN contains strings that are easily converted to resource_type and cloud_local_id in a
CZRN.  The examples contain "trail/mytrailname" and "channel:3456789" which easily convert by splitting on the
delimiter.

The remaining ARNs are often expressed using a RESTful approach, where the ID may be buried in some sort of path.
For these, we must usually return multiple pieces of information in order to properly identify the resource without
ambiguity.  Among these types of ARNs there are a few variations on the theme, with information delimited in different
ways and in different orders.

Consider this example:
arn:aws:elasticloadbalancing:us-east-1:123456789012:loadbalancer/net/my-lb/50dc6c495c0c9188

If you split up the ARN naively, you are left with "loadbalancer/net/my-lb/50dc6c495c0c9188" as the source for the
cloud local ID; however the desired cloud local ID (my-lb) is buried in an arbitrary position in the string.
Hence, for many services we need custom functions to pull out these details.

Each of these custom functions, whether named or anonymous, take 2 arguments.  The first, usually labeled
`arn_resource`, represents this unpredictable part of the ARN: "loadbalancer/net/my-lb/50dc6c495c0c9188".
We also have a second argument `resource_type` which is the value of the resource_type that will be used in the CZRN.
In some cases, we must extract the cloud local ID in different ways from the `arn_resource` based on resource_type.
Despite looking really arcane, all these custom functions are doing is trying to figure out where the useful
cloud local ID information is in the ARN.
"""
_cloud_local_id_custom_handlers = {
    'a4b': lambda r, t: '|'.join(r.split('/')[1:]),
    'acm-pca': _extract_acm_pca_resource_types,
    'airflow': _extract_airflow_cloud_local_ids,
    'appstream': _extract_appstream_cloud_local_ids,
    'apigateway': _extract_apigateway_cloud_local_ids,
    'appsync': lambda r, t: '|'.join(r.split('/')[1::(len(r.split('/')) - 2) or 1]),
    'artifact': lambda r, t: '/'.join(r.split('/')[1:]),
    'autoscaling': _extract_autoscaling_cloud_local_ids,
    'cassandra': _extract_cassandra_cloud_local_ids,
    'cloudformation': _extract_cloudformation_cloud_local_ids,
    'logs': _extract_logs_cloud_local_ids,
    'codebuild': _extract_codebuild_cloud_local_ids,
    'codecommit': lambda r, t: r,
    'cognito-identity': lambda r, t: r.split('/')[2],
    'cognito-sync': _extract_cognito_sync_cloud_local_ids,
    'codepipeline': lambda r, t: r,
    'codestar': lambda r, t: r,
    'codeartifact': lambda r, t: r.split('/', 1)[1],
    'datasync': _extract_datasync_cloud_local_ids,
    'dynamodb': _extract_dynamodb_cloud_local_ids,
    'elasticbeanstalk': _extract_elasticbeanstalk_cloud_local_ids,
    'elasticloadbalancing': _extract_elasticloadbalancing_cloud_local_ids,
    'ecs': _extract_ecs_cloud_local_ids,
    'ecr-public': _extract_ecr_public_cloud_local_ids,
    'ecr': _extract_ecr_public_cloud_local_ids,
    'eks': lambda r, t: r.split('/')[1],
    'events': lambda r, t: r.split('/', 1)[1],
    'batch': lambda r, t: r.split('/')[1].replace(':', "|"),
    'glue': _extract_glue_cloud_local_ids,
    'iam': _extract_iam_cloud_local_ids,
    'kafka': lambda r, t: r.split('/')[2],
    'kms': _extract_kms_cloud_local_ids,
    'sts': lambda r, t: r.split('/')[1],
    'kinesis': _extract_kinesis_cloud_local_ids,
    'kinesisvideo': lambda r, t: '|'.join(r.split('/')[1:]),
    'lambda': _extract_lambda_cloud_local_ids,
    'macie': _extract_macie_cloud_local_ids,
    'macie2': _extract_macie_cloud_local_ids,
    'mediastore': lambda r, t: '/'.join(r.split('/')[1:]),
    'opsworks-cm': lambda r, t: r.split('/')[2],
    'organizations': _extract_organizations_cloud_local_ids,
    'mq': lambda r, t: r.split(':')[-1:][0],
    'mobiletargeting': lambda r, t: r.split('/')[-1:][0],
    'rds': _extract_rds_cloud_local_ids,
    'redshift': _extract_redshift_cloud_local_ids,
    'route53resolver': _extract_route53resolver_cloud_local_ids,
    'secretsmanager': _extract_secretsmanager_cloud_local_ids,
    'securityhub': lambda r, t: '/'.join(r.split('/')[1:]),
    'serverlessrepo': _extract_serverlessrepo_cloud_local_ids,
    'sns': _extract_sns_cloud_local_ids,
    'sqs': lambda r, t: r,
    's3': _extract_s3_cloud_local_ids,
    'swf': lambda r, t: r.split('/')[2],
    'states': lambda r, t: '|'.join(r.split(':')[1:]),
    'storagegateway': _extract_storagegateway_cloud_local_ids,
    'timestream': _extract_timestream_cloud_local_ids,
    'transfer': _extract_transfer_cloud_local_ids,
    'trustedadvisor': lambda r, t: r.split('/')[-1:][0],
    'wafv2': _extract_wafv2_cloud_local_ids,
}
