import troposphere.apigateway as agw
import troposphere.route53 as r53

from .common import *
from .shared import (Parameter, get_endvalue, get_expvalue, get_subvalue,
                     auto_get_props, get_condition, add_obj)
from .lambdas import LambdaPermissionApiGateway
from .iam import IAMPolicyApiGatewayPrivate


class ApiGatewayAccount(agw.Account):
    def __init__(self, title, **kwargs):
        super().__init__(title, **kwargs)
        self.CloudWatchRoleArn = get_expvalue('RoleApiGatewayCloudWatch')


class ApiGatewayResource(agw.Resource):
    def __init__(self, title, key, stage, **kwargs):
        super().__init__(title, **kwargs)
        mapname = f'ApiGatewayStage{stage}ApiGatewayResource'

        auto_get_props(self)
        self.RestApiId = Ref('ApiGatewayRestApi')

        # if stage override ApiGatewayResource specs, get it
        try:
            getattr(cfg, mapname)
        except Exception:
            pass
        else:
            auto_get_props(self, mapname)


class ApiGatewayMethod(agw.Method):
    def __init__(self, title, key, basename, name, stage, **kwargs):
        super().__init__(title, **kwargs)
        mapname = (f'ApiGatewayStage{stage}'
                   f'ApiGatewayResource{basename}Method{name}')

        try:
            getattr(cfg, mapname)
        except Exception:
            pass
        else:
            auto_get_props(self, mapname)

        auto_get_props(self)
        self.RestApiId = Ref('ApiGatewayRestApi')
        self.ResourceId = Ref(f'ApiGatewayResource{basename}')

        # If Uri is a lambda self.Integration.Uri will be like:
        # 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaName.Arn}/invocations'
        # i need to append stage version (Ex. v1) to the Lambda Name so that
        # ${LambdaName.Arn} -> ${LambdaNameV1.Arn}
        if ':lambda:' in self.Integration.Uri:
            iu = self.Integration.Uri
            dot_found = iu.rfind('.')
            before_dot = iu[0:dot_found]
            after_dot = iu[dot_found:]
            if after_dot.startswith('.Arn'):
                self.Integration.Uri = Sub(f'{before_dot}{stage}{after_dot}')


class ApiGatewayStage(agw.Stage):
    def __init__(self, title, name, key, **kwargs):
        super().__init__(title, **kwargs)
        self.StageName = name

        auto_get_props(self)

        self.RestApiId = Ref('ApiGatewayRestApi')
        self.DeploymentId = Ref(f'ApiGatewayDeployment{name}')
        self.DeploymentId = If(
            f'Deployment{name}A',
            Ref(f'ApiGatewayDeployment{name}A'),
            Ref(f'ApiGatewayDeployment{name}B'),
        )


class ApiGatewayDeployment(agw.Deployment):
    def __init__(self, title, name, key, **kwargs):
        super().__init__(title, **kwargs)
        lastresource = next(reversed(list(cfg.ApiGatewayResource)))
        lastmethod = (next(
            reversed(list(cfg.ApiGatewayResource[lastresource]['Method']))))
        self.DependsOn = f'ApiGatewayResource{lastresource}Method{lastmethod}'
        self.Description = Ref(f'Deployment{name}Description')
        self.RestApiId = Ref('ApiGatewayRestApi')


def AGW_Account(key):
    # Resources
    R_Account = ApiGatewayAccount('ApiGatewayAccount')

    add_obj([
        R_Account])


def AGW_DomainName(key):
    for n, v in getattr(cfg, key).items():
        if not v['IBOX_ENABLED']:
            continue

        resname = f'{key}{n}'
        # resources
        r_Domain = agw.DomainName(resname)
        auto_get_props(r_Domain)

        r_r53 = r53.RecordSetType(resname)
        if 'REGIONAL' in v['EndpointConfiguration']['Types']:
            auto_get_props(r_r53, 'R53RecordSetApiGatewayDomainNameRegional')
        else:
            auto_get_props(r_r53, 'R53RecordSetApiGatewayDomainNameGlobal')
        r_r53.title = f'RecordSet{resname}'

        # outputs
        o_Domain = Output(resname)
        o_Domain.Value = Ref(resname)
        o_Domain.Export = Export(resname)

        add_obj([
            r_Domain,
            r_r53,
            o_Domain])


def AGW_UsagePlans(key):
    for n, v in getattr(cfg, key).items():
        resname = f'{key}{n}'
        for m, w in v['ApiStages'].items():
            # parameters
            p_Stage = Parameter(f'{resname}ApiStages{m}Stage')
            p_Stage.Description = (
                f'{m} Stage - empty for default based on env/role')

            add_obj(p_Stage)

        # resources
        r_UsagePlan = agw.UsagePlan(resname)
        auto_get_props(r_UsagePlan)

        # outputs
        o_UsagePlan = Output(resname)
        o_UsagePlan.Value = Ref(resname)
        o_UsagePlan.Export = Export(resname)

        add_obj([
            r_UsagePlan,
            o_UsagePlan])


def AGW_ApiKeys(key):
    for n, v in getattr(cfg, key).items():
        resname = f'{key}{n}'
        # parameters
        p_Enabled = Parameter(f'{resname}Enabled')
        p_Enabled.Description = (
            f'{resname}Enabled - empty for mapped value')
        p_Enabled.AllowedValues = ['', 'yes', 'no']

        p_UsagePlan = Parameter(f'{resname}UsagePlan')
        p_UsagePlan.Description = (
            f'{resname}UsagePlan - empty for default based on env/role')

        add_obj([
            p_Enabled,
            p_UsagePlan])

        # resources
        r_ApiKey = agw.ApiKey(resname)
        auto_get_props(r_ApiKey)

        if 'UsagePlan' in v:
            plankey_name = f'{resname}UsagePlan'
            r_UsagePlanKey = agw.UsagePlanKey(f'ApiGatewayUsagePlan{n}')
            r_UsagePlanKey.KeyId = Ref(resname)
            r_UsagePlanKey.KeyType = 'API_KEY'
            r_UsagePlanKey.UsagePlanId = ImportValue(
                get_subvalue(
                    'ApiGatewayUsagePlan${1M}', f'{resname}UsagePlan')
            )

            add_obj(r_UsagePlanKey)

        # outputs
        o_ApiKey = Output(resname)
        o_ApiKey.Value = Ref(resname)

        add_obj([
            r_ApiKey,
            o_ApiKey])


def AGW_Stages(key):
    for n, v in getattr(cfg, key).items():
        resname = f'{key}{n}'
        depname = f'Deployment{n}'
        # parameters
        p_DeploymentDescription = Parameter(f'{depname}Description')
        p_DeploymentDescription.Description = f'{depname} Description'
        p_DeploymentDescription.Default = n

        p_Deployment = Parameter(depname)
        p_Deployment.Description = (
            f'{depname} - change between A/B '
            'to trigger new deploy')
        p_Deployment.AllowedValues = ['A', 'B']
        p_Deployment.Default = 'A'

        add_obj([
            p_DeploymentDescription,
            p_Deployment])

        # conditons
        c_DeploymentA = get_condition(
            f'{depname}A', 'equals', 'A', depname, nomap=True)

        c_DeploymentB = get_condition(
            f'{depname}B', 'equals', 'B', depname, nomap=True)

        add_obj([
            c_DeploymentA,
            c_DeploymentB])

        # resources
        r_Stage = ApiGatewayStage(resname, name=n, key=v)

        r_DeploymentA = ApiGatewayDeployment(f'ApiGatewayDeployment{n}A',
                                             name=n, key=v)
        r_DeploymentA.Condition = f'{depname}A'

        r_DeploymentB = ApiGatewayDeployment(f'ApiGatewayDeployment{n}B',
                                             name=n, key=v)
        r_DeploymentB.Condition = f'{depname}B'

        # output
        o_Deployment = Output(depname)
        o_Deployment.Value = Ref(depname)

        add_obj([
            r_Stage,
            r_DeploymentA,
            r_DeploymentB,
            o_Deployment])


def AGW_RestApi(key):
    # Resources
    R_RestApi = agw.RestApi('ApiGatewayRestApi')
    auto_get_props(R_RestApi, f'{key}Base')
    R_RestApi.Policy = IAMPolicyApiGatewayPrivate()

    try:
        condition = cfg.PolicyCondition
        R_RestApi.Policy['Statement'][0]['Condition'] = condition
    except Exception:
        pass

    add_obj([
        R_RestApi,
    ])

    for n, v in cfg.ApiGatewayResource.items():
        resname = f'ApiGatewayResource{n}'
        agw_stage = cfg.ApiGatewayRestApi['Base']['Stage']
        r_Resource = ApiGatewayResource(resname, key=v, stage=agw_stage)

        for m, w in v['Method'].items():
            r_Method = ApiGatewayMethod(f'{resname}Method{m}',
                                        key=w, basename=n,
                                        name=m, stage=agw_stage)

            add_obj([
                r_Resource,
                r_Method])

    for n, v in cfg.Lambda.items():
        r_LambdaPermission = LambdaPermissionApiGateway(
            f'LambdaPermission{n}', name=Ref(f'Lambda{n}'),
            source=Sub('arn:aws:execute-api:${AWS::Region}:'
                       '${AWS::AccountId}:${ApiGatewayRestApi}/*/*/*')
        )

        add_obj(r_LambdaPermission)
