# SPDX-License-Identifier: MPL-2.0
# Copyright 2020-2022 John Mille <john@compose-x.io>
"""
Module to handle the creation of the root EFS stack
"""
from __future__ import annotations
from typing import TYPE_CHECKING
import ecs_composex.common.troposphere_tools
if TYPE_CHECKING:
    from ecs_composex.common.settings import ComposeXSettings
    from ecs_composex.mods_manager import XResourceModule
import warnings
from compose_x_common.aws.efs import EFS_ARN_RE, list_efs_mount_targets
from troposphere import GetAtt, Ref, Select, Sub
from troposphere.ec2 import SecurityGroup
from troposphere.efs import FileSystem, MountTarget
from ecs_composex.common.cfn_params import STACK_ID_SHORT
from ecs_composex.common.stacks import ComposeXStack
from ecs_composex.common.troposphere_tools import build_template
from ecs_composex.compose.x_resources.network_x_resources import NetworkXResource
from ecs_composex.efs.efs_params import (
    CONTROL_CLOUD_ATTR_MAPPING,
    FS_ARN,
    FS_ID,
    FS_MNT_PT_SG_ID,
    FS_PORT,
)
from ecs_composex.resources_import import import_record_properties
from ecs_composex.vpc.vpc_params import STORAGE_SUBNETS, VPC_ID
[docs]def create_efs_stack(settings, new_resources):
    """
    Function to create the root stack and add EFS FS.
    :param ecs_composex.common.settings.ComposeXSettings settings:
    :param list new_resources:
    :return: Root template for EFS
    :rtype: troposphere.Template
    """
    template = build_template("Root for EFS built by ECS Compose-X", [FS_PORT])
    for res in new_resources:
        res_cfn_props = import_record_properties(res.properties, FileSystem)
        res.cfn_resource = FileSystem(res.logical_name, **res_cfn_props)
        res.db_sg = SecurityGroup(
            f"{res.logical_name}SecurityGroup",
            GroupName=Sub(f"{res.logical_name}-${{STACK_ID}}", STACK_ID=STACK_ID_SHORT),
            GroupDescription=Sub(f"SG for EFS {res.cfn_resource.title}"),
            VpcId=Ref(VPC_ID),
        )
        template.add_resource(res.cfn_resource)
        template.add_resource(res.db_sg)
        res.init_outputs()
        res.generate_outputs()
        template.add_output(res.outputs)
    return template 
[docs]class Efs(NetworkXResource):
    """
    Class to represent a Filesystem
    """
    subnets_param = STORAGE_SUBNETS
    def __init__(
        self, name, definition, module: XResourceModule, settings: ComposeXSettings
    ):
        self.db_sg = None
        self.db_secret = None
        self.mnt_targets = []
        self.access_points = []
        self.volume = definition["Volume"]
        super().__init__(name, definition, module, settings)
        self.support_defaults = True
        self.set_override_subnets()
        self.ref_parameter = FS_ID
        self.arn_parameter = FS_ARN
        self.security_group_param = FS_MNT_PT_SG_ID
        self.port_param = FS_PORT
        # self.cloud_control_attributes_mapping = CONTROL_CLOUD_ATTR_MAPPING
[docs]    def init_outputs(self):
        """
        Method to init the DocDB output attributes
        """
        self.output_properties = {
            FS_ID: (self.logical_name, self.cfn_resource, Ref, None),
            FS_ARN: (
                f"{self.logical_name}{FS_ARN.return_value}",
                self.cfn_resource,
                GetAtt,
                FS_ARN.return_value,
            ),
            FS_PORT: (
                f"{self.logical_name}{FS_PORT.title}",
                None,
                FS_PORT.Default,
                False,
            ),
            FS_MNT_PT_SG_ID: (
                f"{self.logical_name}{FS_MNT_PT_SG_ID.return_value}",
                self.db_sg,
                GetAtt,
                FS_MNT_PT_SG_ID.return_value,
            ),
        } 
[docs]    def update_from_vpc(self, vpc_stack, settings=None):
        """
        Override for EFS to update settings from VPC Stack
        :param ecs_composex.vpc.vpc_stack.XStack vpc_stack:
        :param ecs_composex.common.settings.ComposeXSettings settings:
        :return:
        """
        subnets_params = self.subnets_param
        if self.subnets_override:
            for subnet_az in vpc_stack.vpc_resource.azs:
                if subnet_az.title == self.subnets_override:
                    subnets_params = subnet_az
                    break
            else:
                raise KeyError(
                    f"{self.module.res_key}.{self.name} - "
                    f"Override subnet name {self.subnets_override} is not defined in x-vpc",
                    list(vpc_stack.vpc_resource.azs.keys()),
                )
        for count, az in enumerate(vpc_stack.vpc_resource.azs[subnets_params]):
            self.stack.stack_template.add_resource(
                MountTarget(
                    f"{self.logical_name}MountPoint{az.title().strip().split('-')[-1]}",
                    FileSystemId=Ref(self.cfn_resource),
                    SecurityGroups=[GetAtt(self.db_sg, "GroupId")],
                    SubnetId=Select(count, Ref(STORAGE_SUBNETS)),
                )
            )  
[docs]def get_efs_details(efs: Efs, account_id, resource_id: str) -> dict:
    client = efs.lookup_session.client("efs")
    props: dict = {}
    efs_r = client.describe_file_systems(FileSystemId=efs.arn)["FileSystems"][0]
    props[FS_ARN] = efs_r["FileSystemArn"]
    props[FS_ID] = efs_r["FileSystemId"]
    mount_points: list = list_efs_mount_targets(
        session=efs.lookup_session, client=client, FileSystemId=efs.arn
    )
    if not mount_points:
        raise LookupError(
            "{}.{} - No EFS MountTargets for {}".format(
                efs.module.res_key, efs.name, efs.arn
            )
        )
    groups: list = []
    for _mnt in mount_points:
        groups_r = client.describe_mount_target_security_groups(
            MountTargetId=_mnt["MountTargetId"]
        )
        for s_group in groups_r["SecurityGroups"]:
            if s_group not in groups:
                groups.append(s_group)
    if not groups:
        raise LookupError(f"Unable to find groups for MountTargets of {efs.arn}")
    props[FS_MNT_PT_SG_ID] = groups[0]
    return props 
[docs]def lookup_resource(module, resource: Efs, settings: ComposeXSettings):
    resource.lookup_resource(
        EFS_ARN_RE,
        get_efs_details,
        FileSystem.resource_type,
        "elasticfilesystem",
    )
    resource.generate_cfn_mappings_from_lookup_properties()
    resource.generate_outputs()
    settings.mappings[module.mapping_key].update(
        {resource.logical_name: resource.mappings}
    ) 
[docs]class XStack(ComposeXStack):
    """
    Class to represent the root for EFS
    """
    def __init__(
        self, name, settings: ComposeXSettings, module: XResourceModule, **kwargs
    ):
        if module.new_resources:
            stack_template = create_efs_stack(settings, module.new_resources)
            super().__init__(name, stack_template, **kwargs)
            if not hasattr(self, "DeletionPolicy"):
                setattr(self, "DeletionPolicy", module.module_deletion_policy)
        else:
            self.is_void = True
        if module.lookup_resources and module.mapping_key not in settings.mappings:
            settings.mappings[module.mapping_key] = {}
        for resource in module.lookup_resources:
            lookup_resource(module, resource, settings)
        for resource in module.resources_list:
            resource.stack = self