import functions_framework,os,json,random,boto3,time,uuid
from google.cloud import secretmanager
from botocore.exceptions import ClientError
from config import *

def generateResponse(code, body=""):
    return (body, code)

class secretManagerHelper:
    def __init__(self, projectId):
        """
        Initialize the secretManager client.
        """
        self.client = secretmanager.SecretManagerServiceClient()
        self.projectId = projectId

    def getSecret(self, secretName):
        try:
            secretPath = f"projects/{self.projectId}/secrets/{secretName}/versions/latest"
            response = self.client.access_secret_version(name=secretPath)
            secretValue = response.payload.data.decode("UTF-8")
            return ('SUCCESS',secretValue)
        except Exception as e:
            return ('ERROR',e)

@functions_framework.http
def lambda_handler(request):
    lambdaRequestId = str(uuid.uuid4())
    eventJson = request.data.decode("utf-8")
    print(f'[INFO]>[{lambdaRequestId}]>request>{eventJson}')
    
    print(f'[INFO]>[{lambdaRequestId}]>validation')
    if not hasattr(request,"headers") :
        print(f'[ERROR]>[{lambdaRequestId}]>[PX17:2]>validation>request:{eventJson}^auth header missing or auth token empty')
        return generateResponse(401)

    try :
        secretManagerHelperObj = secretManagerHelper(os.environ.get('projectId'))
    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)

    authToken = request.headers.get("Apro-Proxy-Auth-Token")
    if not authToken or authToken != os.environ.get("authToken"):
        print(f'[ERROR]>[{lambdaRequestId}]>[PX17:3]>validation>request:{eventJson}^invalid auth token')
        return generateResponse(401)

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

    if not hasattr(request,"data") or not request.data:
        print(f'[ERROR]>[{lambdaRequestId}]>[PX17:4]>validation>request:{eventJson}^body empty')
        return generateResponse(400)
    
    try:
        requestData = json.loads(request.data.decode("utf-8"))
    except json.JSONDecodeError as e:
        print(f'[ERROR]>[{lambdaRequestId}]>[PX17:5]>validation>request:{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>request:{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':
        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>request:{eventJson}^response:{response}')
        return generateResponse(500)

    requestData["credentials"] = json.loads(response[1])
    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>request:{eventJson}^activationLambdaMapping:{activationLambdaMapping}')

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

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

    attempt = 0
    while attempt < 5:
        try:
            print(f'[INFO]>[{lambdaRequestId}]>invoke-lambda^lambda:{activationLambdaMapping[activationLambdaSourceKey]}')
            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-request:{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-request:{requestData}^exception:{c}')
                return generateResponse(500)
    print(f'[INFO]>[{lambdaRequestId}]>invoke-lambda>done^response:{response}')
    return generateResponse(200)