from typing import Any
from typing import Dict
from typing import List

__func_alias__ = {"list_": "list"}


async def update_vpc_endpoint(
    hub,
    ctx,
    old_state: Dict[str, Any],
    resource_id: str,
    policy_document: str = None,
    route_table_ids: List = None,
    subnet_ids: List = None,
    security_group_ids: List = None,
    private_dns_enabled: bool = None,
    timeout: Dict = None,
) -> Dict[str, Any]:
    """
    Modifies a vpc endpoint. The values subnet_ids, security_group_ids, route_table_ids will actually become
    the respective values for the vpc endpoint. It will error if you try to enable private dns on a non Interface endpoint.

    Args:
        hub:
        ctx:
        old_state: Dict[Text, Any]: The previous state of the vpc_endpoint.
        resource_id(string): AWS VPC Endpoint id.
        policy_document(string): The new policy document for the vpc endpoint
        route_table_ids(List): List of route table ids for the vpc endpoint
        subnet_ids(List): List of subnet ids for the vpc endpoint
        security_group_ids(List): List of security group ids for the vpc endpoint
        private_dns_enabled(bool): Whether to enable or disable private dns for the vpc endpoint
        timeout(Dict, optional): Timeout configuration for updating the endpoint.
            * update (Dict) -- Timeout configuration for updating the endpoint
                * delay -- The amount of time in seconds to wait between attempts.
                * max_attempts -- Customized timeout configuration containing delay and max attempts.

    Returns:
        {"result": True|False, "comment": "A message tuple", "ret": Dict}

    """

    result = dict(comment=(), result=True, ret=None)
    name = old_state.get("name")

    # For if the vpc_endpoint exists
    hub.log.debug(f"Updating aws.ec2.vpc_endpoint: '{name}'")

    if old_state.get("policy_document") and policy_document == None:
        policy_document = old_state["policy_document"]

    if private_dns_enabled == None:
        private_dns_enabled = old_state["private_dns_enabled"]

    is_update_required = (
        policy_document and policy_document != old_state["policy_document"]
    ) or (private_dns_enabled != old_state["private_dns_enabled"])

    find_set_difference = lambda x, y, z: (
        list(set(x).difference(set(y))),
        (z or len(set(x).difference(set(y))) > 0),
    )

    if route_table_ids is None:
        route_tables_to_be_removed = []
        new_route_tables = []
    elif old_state.get("route_table_ids"):
        # find the route tables in route_table_ids that aren't in the old state (these are the new ones)
        new_route_tables, is_update_required = find_set_difference(
            route_table_ids, old_state["route_table_ids"], is_update_required
        )
        # the ones in the old_state not in route_table_ids are the ones to be removed
        route_tables_to_be_removed, is_update_required = find_set_difference(
            old_state["route_table_ids"], route_table_ids, is_update_required
        )
    else:
        new_route_tables = route_table_ids
        route_tables_to_be_removed = old_state.get("route_table_ids", [])
        is_update_required = is_update_required or len(route_table_ids) > 0
    hub.log.debug(
        f"Updating aws.ec2.vpc_endpoint: '{name}'. Route tables passed in: {route_table_ids}. Old route tables: {old_state.get('route_table_ids', None)}"
    )
    hub.log.debug(
        f"Updating aws.ec2.vpc_endpoint: '{name}'. New route tables: {new_route_tables}. Route tables to be removed: {route_tables_to_be_removed}"
    )

    # The logic for subnets and security group's is the same as Route tables
    if subnet_ids is None:
        subnets_to_be_removed = []
        new_subnets = []
    elif old_state.get("subnet_ids"):
        new_subnets, is_update_required = find_set_difference(
            subnet_ids, old_state["subnet_ids"], is_update_required
        )
        subnets_to_be_removed, is_update_required = find_set_difference(
            old_state["subnet_ids"], subnet_ids, is_update_required
        )
    else:
        new_subnets = subnet_ids
        subnets_to_be_removed = old_state.get("subnet_ids", [])
        is_update_required = is_update_required or len(subnet_ids) > 0
    hub.log.debug(
        f"Updating aws.ec2.vpc_endpoint: '{name}'. Subnets passed in: {subnet_ids}. Old subnets: {old_state.get('subnet_ids', None)}"
    )
    hub.log.debug(
        f"Updating aws.ec2.vpc_endpoint: '{name}'. New subnets: {new_subnets}. Subnets to be removed: {subnets_to_be_removed}"
    )

    if security_group_ids is None:
        security_groups_to_be_removed = []
        new_security_groups = []
    elif old_state.get("security_group_ids"):
        new_security_groups, is_update_required = find_set_difference(
            security_group_ids, old_state["security_group_ids"], is_update_required
        )
        security_groups_to_be_removed, is_update_required = find_set_difference(
            old_state["security_group_ids"], security_group_ids, is_update_required
        )
    else:
        new_security_groups = security_group_ids
        security_groups_to_be_removed = old_state.get("security_group_ids", [])
        is_update_required = is_update_required or len(security_group_ids) > 0
    hub.log.debug(
        f"Updating aws.ec2.vpc_endpoint: '{name}'. Security groups passed in: {security_group_ids}. Old security groups: {(old_state.get('security_group_ids', None))}"
    )
    hub.log.debug(
        f"Updating aws.ec2.vpc_endpoint: '{name}'. New security groups: {new_security_groups}. Security groups to be removed: {security_groups_to_be_removed}"
    )

    if not is_update_required:
        return result

    if ctx.get("test", False) and result["result"]:
        plan_state = hub.tool.aws.test_state_utils.generate_test_state(
            enforced_state=old_state,
            desired_state={
                "policy_document": policy_document,
                "route_table_ids": route_table_ids,
                "subnet_ids": subnet_ids,
                "security_group_ids": security_group_ids,
                "private_dns_enabled": private_dns_enabled,
            },
        )
        result["ret"] = {k: v for k, v in plan_state.items() if v is not None}
        result["comment"] = (
            f"Would succeed in modifying aws.ec2.vpc_endpoint '{name}'",
        )
        return result

    ret = await hub.exec.boto3.client.ec2.modify_vpc_endpoint(
        ctx,
        VpcEndpointId=resource_id,
        ResetPolicy=False,
        PolicyDocument=policy_document,
        AddRouteTableIds=new_route_tables,
        RemoveRouteTableIds=route_tables_to_be_removed,
        AddSubnetIds=new_subnets,
        RemoveSubnetIds=subnets_to_be_removed,
        AddSecurityGroupIds=new_security_groups,
        RemoveSecurityGroupIds=security_groups_to_be_removed,
        PrivateDnsEnabled=private_dns_enabled,
    )
    result["result"] = ret["result"]
    result["ret"] = ret["ret"]
    if not result["result"]:
        result["comment"] = result["comment"] + ret["comment"]
        return result
    update_waiter_acceptors = [
        {
            "matcher": "pathAll",
            "expected": "available",
            "state": "success",
            "argument": "VpcEndpoints[].State",
        },
        {
            "matcher": "pathAll",
            "expected": "pending",
            "state": "retry",
            "argument": "VpcEndpoints[].State",
        },
    ]
    waiter_config = hub.tool.aws.waiter_utils.create_waiter_config(
        default_delay=15,
        default_max_attempts=40,
        timeout_config=timeout.get("update") if timeout else None,
    )
    endpoint_waiter = hub.tool.boto3.custom_waiter.waiter_wrapper(
        name="VpcEndpointModify",
        operation="DescribeVpcEndpoints",
        argument=["VpcEndpoints[].State"],
        acceptors=update_waiter_acceptors,
        client=hub.tool.boto3.client.get_client(ctx, "ec2"),
    )
    try:
        await hub.tool.boto3.client.wait(
            ctx,
            "ec2",
            "VpcEndpointModify",
            endpoint_waiter,
            VpcEndpointIds=[resource_id],
            WaiterConfig=waiter_config,
        )
    except Exception as e:
        result["comment"] = result["comment"] + (str(e),)
        result["result"] = False
        return result

    result["comment"] = (f"Succeeded modifying aws.ec2.vpc_endpoint '{name}'",)
    return result


async def get(
    hub,
    ctx,
    name,
    resource_id: str,
) -> Dict:
    """
    Use an un-managed vpc_endpoint as a data-source. Supply resource_id as a filter.

    Args:
        name(Text): The name of the Idem state.
        resource_id(Text):  An AWS vpc_endpoint resource_id to identify the resource.
    """

    result = dict(comment=[], ret=None, result=True)

    resource_ret = await hub.exec.boto3.client.ec2.describe_vpc_endpoints(
        ctx, VpcEndpointIds=[resource_id]
    )

    if not resource_ret["result"]:
        if "InvalidVpcEndpointId.NotFound" in str(resource_ret["comment"]):
            result["comment"].append(
                hub.tool.aws.comment_utils.get_empty_comment(
                    resource_type="aws.ec2.vpc_endpoint", name=name
                )
            )
            result["comment"] += list(resource_ret["comment"])
            return result
        result["comment"] += list(resource_ret["comment"])
        result["result"] = resource_ret["result"]
        return result

    if not resource_ret["ret"]["VpcEndpoints"]:
        result["comment"].append(
            hub.tool.aws.comment_utils.get_empty_comment(
                resource_type="aws.ec2.vpc_endpoint", name=name
            )
        )
        return result

    result[
        "ret"
    ] = hub.tool.aws.ec2.conversion_utils.convert_raw_vpc_endpoint_to_present(
        raw_resource=resource_ret["ret"]["VpcEndpoints"][0], idem_resource_name=name
    )

    return result


async def list_(hub, ctx, name) -> Dict:
    """
    Use an un-managed VPC Endpoints as a data-source.

    Args:
        name(string): The name of the Idem state.

    """
    result = dict(comment=[], ret=[], result=True)
    ret = await hub.exec.boto3.client.ec2.describe_vpc_endpoints(ctx)
    if not ret["result"]:
        result["comment"] += list(ret["comment"])
        result["result"] = False
        return result
    if not ret["ret"]["VpcEndpoints"]:
        result["comment"].append(
            hub.tool.aws.comment_utils.list_empty_comment(
                resource_type="aws.ec2.vpc_endpoint", name=name
            )
        )
        return result
    for vpc_endpoint in ret["ret"]["VpcEndpoints"]:
        resource_id = vpc_endpoint.get("VpcEndpointId")
        converted_resource = (
            hub.tool.aws.ec2.conversion_utils.convert_raw_vpc_endpoint_to_present(
                vpc_endpoint, idem_resource_name=resource_id
            )
        )
        result["ret"].append(converted_resource)
    return result
