""" API Call Demo is used to demonstrate how to use the Open APIs of FlashCone for the access to the FlashCone solutions. 
    You could run this python code to make the API Call to get the customer information of "Customer_Demo". 
        > python api_call_demo.py
        
    The customer information will be shown as below after a successful response is received:
    {
        'partnership': None, 
        'name': 'Customer_Demo', 
        'ingestDomain': '', 
        'industry': 'Cloud Service Provider', 
        'id': 35394, 
        'type': 2, 
        'region': 'ID', 
        'parentId': 2, 
        'onboardingType': 1
    }

    For a sole demo purpose, FlashCone platform had provided a demo customer (Customer ID: 35394) with a live Access Key to 
    illustrate making the API Call. 
    Please don't use this Access Key and demo customer information in any other cases with any improper intents.

    For details on a specific REST API, please refer to the documentation at https://docs.flashcone.com
    
    Copyright © 2026 FlashCone Technologies. All Rights Reserved.
"""
#coding=utf-8


import hashlib
import hmac
import json
import traceback
from datetime import datetime
from datetime import timezone
from random import randint
import requests


def debug(message):
    """Print out for debugging

    Args:
        message (string): message to be printed out
    """
    
    if True:
        print("[ " + datetime.now().strftime('%Y%m%d %H:%M:%S') + " ]: ====================>\n", message)

def print_request(request):
    """Print out the request.

    Args:
        request (requests.Request): requst object
    """
    
    print("================== Request =====================")
    print(request.method + " " + request.path_url + " HTTP/1.1")
    print("\r\n".join(f"{key}: {value}" for key, value in request.headers.items()))
    print("")
    print(request.body)

def print_response(response):
    """Print out the response

    Args:
        response (requests.Response): response object
    """

    print("==================== Response ===================")
    print("HTTP/1.1 {} {}".format(response.status_code, response.reason))
    print("\r\n".join(f"{key}: {value}" for key, value in response.headers.items()))
    print("")
    print(response.text)

def generate_signature(access_key_id, access_key_secret, signature_version, method, host, request_uri, custom_headers, request_body):
    """Sign the canonical string to generate the signature with the Access key.

    Args:
        access_key_id (string): Access Key ID
        access_key_secret (string): Access Key Secret
        signature_version (int): version of the signature, 1 or 2
        method (string): HTTP method of the reuqest, "GET", "POST", "DELETE", "HEAD", "PUT" and "PATCH"
        request_uri (string): the uri to identify the API with Query string in the format of absolute URI or absolute path
        custom_headers (string): custom header as Header Parameters. The header names starting with "X-SFD-" will be used to generate signature
        request_body (string): the Request Body in JSON format

    Returns:
        signature (string): signature in HEX format
        canonical_headers (dict): canonical headers used for signature
    """

    # Take the signature version as 2 except an explicit indication of version 1 
    if signature_version != 1:
        signature_version = 2

    # Get the Request-URI in the format of absolute path
    path_url = request_uri if request_uri != None else "/"
    if ( not path_url.lower().startswith("/") ):
        path_url = '/' + path_url.split('/', 3)[3]
    
    # generate the canonical headers    
    canonical_headers = {}
    # add the Common Headers with header names starting with "X-SFD-"
    canonical_headers["X-SFD-Date"] = datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')
    canonical_headers["X-SFD-Nonce"] = str(randint(10000, 99999))
    if (signature_version == 2):
        # For signature v2, canonical headers include Host, X-SFD-Date, X-SFD-Nonce, X-SFD-Signature-Version, custom headers starting with "X-SFD-"
        canonical_headers["Host"] = host
        canonical_headers["X-SFD-Signature-Version"] = str(signature_version)
        # add the Custom headers with header names starting with "X-SFD-"
        if (custom_headers != None):
            for header_name in custom_headers:
                if (header_name.lower().startswith("x-sfd-")):
                    canonical_headers[header_name] = custom_headers[header_name]

    # Concatenate all the canonical_headers after sorting by header names
    canonical_headers_str = ""
    for header_name in sorted(canonical_headers.keys()):
        canonical_headers_str += "{}:{}\n".format(header_name.lower(), canonical_headers[header_name])
    
    # Concatenate to generate the canonical string to be signed
    if (method == None):
        method = "GET"
        
    # Signature will sign with: method, abs_path, canonical headers (v1 or v2) in order, Access Key ID, Request Body
    canonical_str_to_be_signed = "{}\n{}\n{}{}\n{}".format(method.upper(), path_url, canonical_headers_str, access_key_id, request_body)

    # Convert to HEX format after signing the Canonical str with the algorithm of HMAC-SHA256
    signature = hmac.new(access_key_secret.encode('utf-8'), canonical_str_to_be_signed.encode('utf-8'), digestmod=hashlib.sha256).hexdigest()

    return signature, canonical_headers



def fire_api_call(access_key_id, access_key_secret, signature_version, method, host, request_uri, custom_headers, request_body):
    """Fire an API Call with the parameters required to the API Gateway. 

    Args:
        access_key_id (string): Access Key ID
        access_key_secret (string): Access Key Secret
        signature_version (int): version of the signature, 1 or 2
        method (string): HTTP method of the reuqest, "GET", "POST", "DELETE", "HEAD", "PUT" and "PATCH"
        request_uri (string): the uri to identify the API with Query string in the format of absolute URI or absolute path
        custom_headers (string): custom header as Header Parameters. The header names starting with "X-SFD-" will be used to generate signature
        request_body (string): the Request Body in JSON format

    Returns:
        status_code: HTTP status code
        data: data in JSON format, including error code and error message
    """


    # Generate the signature
    signature, canonical_headers = generate_signature(access_key_id, access_key_secret, signature_version, method, host, request_uri, custom_headers, request_body)

    # Construct the HTTP headers
    request_headers = {}
    request_headers["Host"] = host
    request_headers["Content-Type"] = "application/json; charset=utf-8"
    request_headers["Authorization"] = "HMAC-SHA256 {}:{}".format(access_key_id, signature)
    request_headers.update(custom_headers)
    request_headers.update(canonical_headers)

    try:
        # print out the request
        print_request(requests.Request(method, request_uri, data=request_body, headers=request_headers).prepare())

        rs = None
        match method.upper():
            case "POST":
                rs = requests.post(request_uri, data=request_body, headers=request_headers)
            case "DELETE":
                rs = requests.delete(request_uri, data=request_body, headers=request_headers)
            case "PUT":
                rs = requests.put(request_uri, data=request_body, headers=request_headers)
            case "HEAD":
                rs = requests.head(request_uri, data=request_body, headers=request_headers)
            case "PATCH":
                rs = requests.patch(request_uri, data=request_body, headers=request_headers)
            case _:
                rs = requests.get(request_uri, data=request_body, headers=request_headers)

        # print out the response
        print_response(rs) 
        
        # Get status code
        status_code = rs.status_code
        # Get the Trace ID
        trace_id = rs.headers["x-sfd-trace-id"]
        # get error code and error message
        data = json.loads(rs.text)
                
        return status_code, trace_id, data
    except requests.RequestException as e:
        raise e


# Settings for API Call Demo
# API gateway
api_gateway = "open-api.flashcone.com"

# Path-URL to get the customer information
path_url = "/v1.1/customer/{customer_id}"

# Acces key is used as the credentials to sign the API request for authentication.
# Please log into FlashCone Portal with your account and password to obtain your Access Key. 
# For a demo purpose, the key pair of a live demo account from FlashCone Portal will be used.
# Please don't use this Access Key and demo customer information in any other cases with any improper intents.
access_key_id = "O80ybSq26xUE383u"
access_key_secret = "q738531SV3s0yFC2I3p7QJ49og37yIat"

# Fire the API Call
try:
    # Prepare the Path parameters
    customer_id = "35394"
    path_url = path_url.format(customer_id=customer_id)
    
    # Prepare the absolute Request-URI
    request_uri = "https://" + api_gateway + path_url
    
    # Assume we need to pass a custom header: X-SFD-FZone: ID
    custom_headers = {"X-SFD-FZone" : "ID"}
    
    # Fire the API Call
    status_code, trace_id, customer_info = fire_api_call(access_key_id, access_key_secret, 2, "GET", api_gateway, request_uri, custom_headers, "")

    if status_code == 200:
        debug(customer_info)
    else:
        debug("Error Code: {}".format(customer_info["code"]))
        debug("Error message: {}".format((customer_info["message"])))
except Exception as e:
    debug(e)

