import json,os,random,boto3,time
from botocore.exceptions import ClientError
from config import *

def generateResponse(code, body=""):
    response = {
        "statusCode" : code
    }
    
    if body:
        response["body"] = body

    return response

class secretManagerHelper:
    def __init__(self, awsRegion):
        """
        Initialize the secretManager client.
        """
        session = boto3.session.Session()
        self.client = session.client(
            service_name='secretsmanager',
            region_name=awsRegion
        )

    def getSecret(self,secretName):
        try:
            secretResponse = self.client.get_secret_value(
                SecretId=secretName
            )
            return ('SUCCESS',secretResponse)
        except ClientError as e:
            return ('ERROR',e)

def lambda_handler(event, context):
    lambdaRequestId = f'{context.aws_request_id}'
    eventJson = json.dumps(event)
    print(f'[INFO]>[{lambdaRequestId}]>validation')
        
    if "headers" not in event or "APRO-PROXY-AUTH-TOKEN" not in event["headers"] or not event["headers"]["APRO-PROXY-AUTH-TOKEN"]:
        print(f'[ERROR]>[{lambdaRequestId}]>[PX17:2]>validation>event:{eventJson}^auth header missing or auth token empty')
        return generateResponse(401)
    
    """
    below code requires google-cloud-secret-manager library to run
    """
    try :
        secretManagerHelperObj = secretManagerHelper(awsRegion)
    except Exception as e:
        print(f'[ERROR]>[{lambdaRequestId}]>[PX17:12]>get-secret-value>event:{eventJson}^exception:{e}')
        return generateResponse(500)

    response = secretManagerHelperObj.getSecret('aproApiKey')
    if response[0] == 'ERROR':
        print(f'[ERROR]>[{lambdaRequestId}]>[PX17:13]>validation>event:{eventJson}^response:{response}')
        return generateResponse(500)

    if event["headers"]["APRO-PROXY-AUTH-TOKEN"] != response[1]['SecretString']:
        print(f'[ERROR]>[{lambdaRequestId}]>[PX17:3]>validation>event:{eventJson}^invalid auth token')
        return generateResponse(401)

    if "httpMethod" not in event \
        or  event['httpMethod'] != 'POST':
            print(f'[ERROR]>[{lambdaRequestId}]>[PX17:1]>validation>event:{eventJson}^invalid method')
            return generateResponse(405)

    if "body" not in event:
        print(f'[ERROR]>[{lambdaRequestId}]>[PX17:4]>validation>event:{eventJson}^body empty')
        return generateResponse(400)
    
    try:
        requestData = json.loads(event["body"])
    except json.JSONDecodeError as e:
        print(f'[ERROR]>[{lambdaRequestId}]>[PX17:5]>validation>event:{eventJson}^exception:{e}')
        return generateResponse(400)
        
    if "context" not in requestData \
        or "source_id" not in requestData["context"] \
        or "action" not in requestData["context"]:
        print(f'[ERROR]>[{lambdaRequestId}]>[PX17:6]>validation>event:{eventJson}^required data not found in context')
        return generateResponse(400)

    print(f'[INFO]>[{lambdaRequestId}]>get-secret-value>advertiser id')
    secretName = f'apro/{requestData["context"]["source_id"]}/{requestData["context"]["source_advertiser_id"]}'
    print(f'[INFO]>[{lambdaRequestId}]>get-secret-value>advertiser id^secretName:{secretName}')

    response = secretManagerHelperObj.getSecret(secretName)
    if response[0] == 'ERROR' and response[1].response['Error']['Code'] in ["ResourceNotFoundException", "InvalidRequestException"]:
        print(f'[INFO]>[{lambdaRequestId}]>get-secret-value>advertiser id data not found. fallback to source secret')
        secretName = f'apro/{requestData["context"]["source_id"]}'
        print(f'[INFO]>[{lambdaRequestId}]>get-secret-value>source-secret^secretName:{secretName}')
        response = secretManagerHelperObj.getSecret(secretName)
    
    if response[0] == 'ERROR':
        print(f'[ERROR]>[{lambdaRequestId}]>[PX17:7]>validation>event:{eventJson}^response:{response}')
        return generateResponse(500)

    requestData["credentials"] = json.loads(response[1]['SecretString'])
    print(f'[INFO]>[{lambdaRequestId}]>get-secret-value>done')
    
    print(f'[INFO]>[{lambdaRequestId}]>invoke-lambda')
    activationLambdaSourceKey = f"{requestData['context']['source_id']}_{requestData['context']['action']}"
    print(f'[INFO]>[{lambdaRequestId}]>invoke-lambda^activationLambdaSourceKey:{activationLambdaSourceKey}')
    if activationLambdaSourceKey not in activationLambdaMapping or not activationLambdaMapping[activationLambdaSourceKey]:
        print(f'[ERROR]>[{lambdaRequestId}]>[PX17:11]>invoke-lambda>event:{eventJson}^activationLambdaMapping:{activationLambdaMapping}')
        return generateResponse(500)

    response = secretManagerHelperObj.getSecret('aproAwsAccessKey')
    if response[0] == 'ERROR':
        print(f'[ERROR]>[{lambdaRequestId}]>[PX17:14]>invoke-lambda>event:{eventJson}^response:{response}')
        return generateResponse(500)
    awsAccessKey = response[1]['SecretString']

    response = secretManagerHelperObj.getSecret('aproAwsSecretKey')
    if response[0] == 'ERROR':
        print(f'[ERROR]>[{lambdaRequestId}]>[PX17:15]>invoke-lambda>event:{eventJson}^response:{response}')
        return generateResponse(500)
    awsSecretKey = response[1]['SecretString']

    attempt = 0
    while attempt < 5:
        try:
            print(f'[INFO]>[{lambdaRequestId}]>invoke-lambda^lambda:{activationLambdaMapping[activationLambdaSourceKey]}')
            """
            below code requires boto3 library to run
            """
            lambdaClient = boto3.client(
                'lambda',
                aws_access_key_id=awsAccessKey,
                aws_secret_access_key=awsSecretKey,
                region_name=awsRegion
            )
            response = lambdaClient.invoke(
                FunctionName=f"{activationLambdaMapping[activationLambdaSourceKey]}",
                InvocationType='Event',
                Payload=json.dumps(requestData)
            )
            break
        except ClientError as c:
            if c.response['Error']['Code'] == 'TooManyRequestsException':
                time.sleep(random.randint(50,100)/1000)
                attempt += 1
                print(f'[INFO]>[{lambdaRequestId}]>invoke-lambda>attempt:{attempt}^exception:{c}')
                if attempt == 5:
                    '''for security reason credentials can not be logged'''
                    del requestData["credentials"]
                    print(f'[ERROR]>[{lambdaRequestId}]>[PX17:8]>invoke-lambda>attempt:{attempt}^lambda-event:{requestData}^exception:{c}')
                    return generateResponse(500)        
            else:
                '''for security reason credential can not be logged'''
                del requestData["credentials"]
                print(f'[ERROR]>[{lambdaRequestId}]>[PX17:9]>invoke-lambda>lambda-event:{requestData}^exception:{c}')
                return generateResponse(500)
    print(f'[INFO]>[{lambdaRequestId}]>invoke-lambda>done^response:{response}')
    return generateResponse(200)