import troposphere.awslambda as lbd

from .common import *
from .shared import (
    Parameter,
    get_endvalue,
    get_expvalue,
    get_subvalue,
    auto_get_props,
    get_condition,
    add_obj,
    import_lambda,
)
from .iam import IAMRoleLambdaBase


class LambdaPermission(lbd.Permission):
    def __init__(self, title, **kwargs):
        super().__init__(title, **kwargs)
        self.Action = "lambda:InvokeFunction"


class LambdaPermissionEvent(LambdaPermission):
    def __init__(self, title, key, source, **kwargs):
        super().__init__(title, **kwargs)
        self.Principal = "events.amazonaws.com"
        self.FunctionName = eval(key["Arn"])
        self.SourceArn = GetAtt(source, "Arn")


class LambdaPermissionS3(LambdaPermission):
    def __init__(self, title, key, source, **kwargs):
        super().__init__(title, **kwargs)
        self.Principal = "s3.amazonaws.com"
        self.FunctionName = key
        self.SourceArn = Sub("arn:aws:s3:::%s" % source)


class LambdaPermissionSNS(LambdaPermission):
    def __init__(self, title, key, **kwargs):
        super().__init__(title, **kwargs)
        self.Principal = "sns.amazonaws.com"
        self.FunctionName = eval(key["Endpoint"])
        self.SourceArn = eval(key["TopicArn"])


class LambdaPermissionApiGateway(LambdaPermission):
    def __init__(self, title, name, source, **kwargs):
        super().__init__(title, **kwargs)
        self.Principal = "apigateway.amazonaws.com"
        self.FunctionName = name
        self.SourceArn = source


class LambdaPermissionLoadBalancing(LambdaPermission):
    def __init__(self, title, name, **kwargs):
        super().__init__(title, **kwargs)
        self.Principal = "elasticloadbalancing.amazonaws.com"
        self.FunctionName = name


class LambdaVersion(lbd.Version):
    def __init__(self, title, name, **kwargs):
        super(LambdaVersion, self).__init__(title, **kwargs)
        self.FunctionName = Ref(name)


class LambdaFunction(lbd.Function):
    def setup(self, key, name):
        import_name = key.get("ImportName", name)
        if "Code" not in key:
            self.Code = lbd.Code()
            try:
                self.Code.ZipFile = Join("", import_lambda(import_name))
            except Exception:
                self.Code.ZipFile = (
                    'print("Use Code parameter in yaml '
                    f"or create file lib/lambas/{import_name}.code "
                    'with lambda code to execute.")'
                )
        auto_get_props(self)
        self.FunctionName = Sub("${AWS::StackName}-${EnvRole}-%s" % name)
        if "Handler" not in key:
            self.Handler = "index.lambda_handler"
        try:
            getattr(self, "Role")
        except Exception:
            self.Role = GetAtt(f"RoleLambda{name}", "Arn")

        if all(k in key for k in ["SecurityGroupIds", "SubnetIds"]):
            self.VpcConfig = lbd.VPCConfig("")
            auto_get_props(self.VpcConfig, self.title)

        # Variables - skip if atEdge - always set Env, EnvRole
        try:
            key["AtEdge"]
        except Exception as e:
            self.Environment = lbd.Environment(
                Variables={
                    "Env": Ref("EnvShort"),
                    "EnvRole": Ref("EnvRole"),
                }
            )
            if "Variables" in key:
                self.Environment.Variables.update(
                    {
                        varname: get_endvalue(f"{self.title}Variables{varname}")
                        for varname in key["Variables"]
                    }
                )


class LambdaLayerVersionPermission(lbd.LayerVersionPermission):
    def __init__(self, title, **kwargs):
        super().__init__(title, **kwargs)
        self.Action = "lambda:GetLayerVersion"
        self.Principal = Ref("AWS::AccountId")


def LambdaLayers(value):
    # parameters
    p_Layer = Parameter(value)
    p_Layer.Description = value

    add_obj(p_Layer)

    # condition
    add_obj(get_condition(value, "not_equals", ""))

    # output
    o_Layer = Output(value)
    o_Layer.Value = get_endvalue(value, condition=True)

    add_obj(o_Layer)

    return o_Layer.Value


##


def LBD_Lambdas(key):
    # Resources
    for n, v in getattr(cfg, key).items():
        resname = f"{key}{n}"

        try:
            v["Code"]["S3Key"]
        except Exception:
            pass
        else:
            s3keyname = f"{resname}CodeS3Key"
            # parameters
            p_S3Key = Parameter(s3keyname)
            p_S3Key.Description = f"S3Key Name for lambda {n} Code"

            add_obj(p_S3Key)

            # outputs
            o_S3Key = Output(s3keyname)
            o_S3Key.Value = get_endvalue(s3keyname)

            add_obj(o_S3Key)

        # resources
        r_Lambda = LambdaFunction(resname)
        r_Lambda.setup(key=v, name=n)

        if "Enabled" in v:
            # conditions
            add_obj(get_condition(resname, "equals", "yes", f"{resname}Enabled"))

            r_Lambda.Condition = resname

        if "Layers" in v:
            r_Lambda.Layers = []
            for i, j in enumerate(v["Layers"]):
                r_Lambda.Layers.append(LambdaLayers(j))

        if "Version" in v:
            versionname = f"{resname}Version"
            versionnameA = f"{versionname}A"
            versionnameB = f"{versionname}B"

            # parameters
            p_Version = Parameter(versionname)
            p_Version.Description = (
                "LambdaVersion - change between A/B " "to force deploy new version"
            )
            p_Version.AllowedValues = ["", "A", "B"]
            p_Version.Default = ""

            add_obj(p_Version)

            # conditons
            c_VersionA = {
                versionnameA: And(
                    Condition(resname) if "Enabled" in v else Equals("1", "1"),
                    get_condition("", "equals", "A", versionname, nomap=True),
                )
            }

            c_VersionB = {
                versionnameB: And(
                    Condition(resname) if "Enabled" in v else Equals("1", "1"),
                    get_condition("", "equals", "B", versionname, nomap=True),
                )
            }

            c_Version = {
                versionname: Or(
                    Condition(versionnameA),
                    Condition(versionnameB),
                )
            }

            add_obj([c_VersionA, c_VersionB, c_Version])

            # resources
            r_VersionA = LambdaVersion(versionnameA, name=resname)
            r_VersionA.Condition = versionnameA

            r_VersionB = LambdaVersion(versionnameB, name=resname)
            r_VersionB.Condition = versionnameB

            # outputs
            o_Version = Output(versionname)
            o_Version.Value = If(versionnameA, Ref(versionnameA), Ref(versionnameB))
            o_Version.Condition = versionname

            add_obj([r_VersionA, r_VersionB, o_Version])

        if not v.get("SkipRole"):
            # Automatically setup a lambda Role with base permissions.
            r_Role = IAMRoleLambdaBase(f"Role{resname}", key=v)
            if hasattr(r_Lambda, "Condition"):
                r_Role.Condition = r_Lambda.Condition
            add_obj(r_Role)

        add_obj(r_Lambda)

        if v.get("Export"):
            O_Lambda = Output(resname)
            O_Lambda.Value = GetAtt(resname, "Arn")
            O_Lambda.Export = Export(resname)

            add_obj(O_Lambda)


def LBD_LayerVersions(key):
    # Resources
    for n, v in getattr(cfg, key).items():
        resname = f"{key}{n}"

        try:
            v["Content"]["S3Key"]
        except Exception:
            pass
        else:
            s3keyname = f"{resname}ContentS3Key"
            # parameters
            p_S3Key = Parameter(s3keyname)
            p_S3Key.Description = f"S3Key Name for lambda {n} Content"

            add_obj(p_S3Key)

            # outputs
            o_S3Key = Output(s3keyname)
            o_S3Key.Value = get_endvalue(s3keyname)

            add_obj(o_S3Key)

        # resources
        r_Layer = lbd.LayerVersion(resname)
        auto_get_props(r_Layer)
        r_LayerPermission = LambdaLayerVersionPermission(f"LambdaLayerPermission{n}")
        r_LayerPermission.LayerVersionArn = Ref(resname)

        # output
        o_Layer = Output(resname)
        o_Layer.Value = Ref(resname)

        add_obj([r_Layer, r_LayerPermission, o_Layer])
