#Import Library
import requests
import datetime
import hashlib
import hmac
import json
import time
import smtplib
import traceback
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart

#send error email
def send_error_email(subject, body):
    sender_email = "admin@hsglobal.com.au"
    receiver_email = "ken.l@hsglobal.com.au"
    app_password = "drtysyfvwgbjlqyk"  # ⚠️ Outlook App Password

    msg = MIMEMultipart()
    msg['From'] = sender_email
    msg['To'] = receiver_email
    msg['Subject'] = subject
    msg.attach(MIMEText(body, 'plain'))

    try:
        with smtplib.SMTP("smtp.office365.com", 587) as server:
            server.starttls()
            server.login(sender_email, app_password)
            server.send_message(msg)
            print(" Error email sent successfully.")
    except Exception as e:
        print(" Failed to send email:", e)

# === Amazon Cofiguration  === #
refresh_token = "Atzr|IwEBIK33GpCH8JrjFCKraLthIv3md1xIYYMZ3bFWeyDRQGh_-yBEk5fZ6enH_4sh_vUQIhN4fDjF8lml7RFkAMqcKRgltXzCi_rVn0u7Mv5UzYi1jbHlBQmjmIBHFhy1iApjFeLldztaG6an6VUZsoqrwn0nWoeRUC3ngLZUpqK9KufgLcfuiE1r7h8QtCmXpdbVJ7c3_ZROcUmb-tjIS7Zp0N4joUb0z7ZsmSNU3JmZKHcgHTIRsrvfZ5tWmcHl1I-I5MNkYRWsfbsIlBB29cAMNL6xhyZBbxt3eMZbF_1hz0dVeGinYw0ihODFs1x8ue0acMo"
client_id = "amzn1.application-oa2-client.f565a6ac7a3149edbb1bf350228d71cc"
client_secret = "amzn1.oa2-cs.v1.b6b9a257cc57aeb1c0e241f81ef8ee8b2aa09e3f068a5b1d2c0184f53b64fcd0"
aws_access_key = "AKIA2ARRMGCRQYK4LF7L"
aws_secret_key = "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"
region = "ap-southeast-2"
service = "execute-api"
marketplace = "sellingpartnerapi-fe.amazon.com"

# === Cin7 Cofiguration === #
cin7_api_url = "https://api.cin7.com/api/v1/SalesOrders"
cin7_api_token = "SFNJbnRlcm5hdGlvbmFsQVU6YWM4ZmI5YjhhYzUzNGI1N2EwZGI0NGY1YjAxOGI3NTE=" #Product
#cin7_api_token = "U2FuZGJveEhTR2xvYmFsQVU6YTAxZTczMDU1MTVhNDUwZDliYjkwZWU5NjFhMjc3ZjI=" #Sanbox

# Interval Time for each request (seconds)
upload_delay = 1

# default_adress
DEFAULT_ADDRESS = "123 Default Street, Sydney, NSW 2000"

# ===== Amazon Warehouse Maping =====
warehouse_address_map = {
    "AVV2": "95 Whitfield Blvd, Cranbourne West, VIC 3977",
    "BNE1": "62 Export Street, Lytton, QLD 4178",
    "BWU1": "23 Centenary Avenue, Moorebank, NSW 2017",
    "BWU2": "13 Emporium Avenue, Kemps Creek, NSW 2178",
    "BWU6": "5 Johnston Crescent, Horsley Park, NSW 2175",
    "MEL1": "29 National Drive, Dandenong South, VIC 3175",
    "MEL5": "103 Palm Springs Rd, Ravenhall, VIC 3023",
    "PER4": "2 Centurion Place, Jandakot, WA 6164",
    "XAU1": "5 Darrabarra Ave, Kemps Creek, NSW 2178 AU",
    "XAU2": "Unit 2 80-96 South Park Drive, Dandenong South, VIC 3175"
}
#Data 
def log(msg):
    print(f"[{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] {msg}")

#Adress Format
def parse_address(address_str):
    parts = address_str.split(", ")
    addr1 = parts[0] if len(parts) >= 1 else ""
    addr2 = ""
    city = parts[1] if len(parts) >= 2 else ""
    state_post = parts[2] if len(parts) >= 3 else ""
    state_parts = state_post.strip().split(" ")
    state = state_parts[0] if len(state_parts) >= 1 else ""
    postcode = state_parts[1] if len(state_parts) >= 2 else ""
    country = "Australia"
    return addr1, addr2, city, state, postcode, country

#Get Amazon Access Token
def get_access_token():
    url = "https://api.amazon.com/auth/o2/token"
    payload = {
        "grant_type": "refresh_token",
        "refresh_token": refresh_token,
        "client_id": client_id,
        "client_secret": client_secret
    }
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    response = requests.post(url, data=payload, headers=headers)
    response.raise_for_status()
    return response.json()["access_token"]

def sign(key, msg):
    return hmac.new(key, msg.encode("utf-8"), hashlib.sha256).digest()

#AWS keys transformat
def get_signature_key(key, dateStamp, regionName, serviceName):
    kDate = sign(("AWS4" + key).encode("utf-8"), dateStamp)
    kRegion = sign(kDate, regionName)
    kService = sign(kRegion, serviceName)
    kSigning = sign(kService, "aws4_request")
    return kSigning

#get Amazon Purchase Orders
#Uncomfirmed 
# #time
def get_amazon_orders_list():
    access_token = get_access_token()
    method = "GET"
    host = marketplace
    endpoint = f"https://{host}/vendor/orders/v1/purchaseOrders?createdAfter=2024-01-01T00:00:00Z"

    t = datetime.datetime.utcnow()
    amz_date = t.strftime("%Y%m%dT%H%M%SZ")
    datestamp = t.strftime("%Y%m%d")
    canonical_uri = "/vendor/orders/v1/purchaseOrders"
    canonical_querystring = "createdAfter=2024-01-01T00:00:00Z"
    canonical_headers = f"host:{host}\nx-amz-access-token:{access_token}\nx-amz-date:{amz_date}\n"
    signed_headers = "host;x-amz-access-token;x-amz-date"
    payload_hash = hashlib.sha256("".encode("utf-8")).hexdigest()
    canonical_request = f"{method}\n{canonical_uri}\n{canonical_querystring}\n{canonical_headers}\n{signed_headers}\n{payload_hash}"
    algorithm = "AWS4-HMAC-SHA256"
    credential_scope = f"{datestamp}/{region}/{service}/aws4_request"
    string_to_sign = f"{algorithm}\n{amz_date}\n{credential_scope}\n{hashlib.sha256(canonical_request.encode('utf-8')).hexdigest()}"
    signing_key = get_signature_key(aws_secret_key, datestamp, region, service)
    signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
    authorization_header = f"{algorithm} Credential={aws_access_key}/{credential_scope}, SignedHeaders={signed_headers}, Signature={signature}"
    headers = {
        "x-amz-access-token": access_token,
        "x-amz-date": amz_date,
        "Authorization": authorization_header
    }

    response = requests.get(endpoint, headers=headers)
    response.raise_for_status()
    data = response.json()

    if "payload" not in data or "orders" not in data["payload"]:
        raise ValueError(" 'orders' no exis，Amazon return format error")

    #  only purchaseOrderState == "New"  PO
    unconfirmed_pos = [
        po["purchaseOrderNumber"]
        for po in data["payload"]["orders"]
        if po.get("purchaseOrderState") == "New"
    ]

    return unconfirmed_pos

#check through list
def get_amazon_order(po_number):
    access_token = get_access_token()
    method = "GET"
    host = marketplace
    endpoint = f"https://{host}/vendor/orders/v1/purchaseOrders/{po_number}"
    t = datetime.datetime.utcnow()
    amz_date = t.strftime("%Y%m%dT%H%M%SZ")
    datestamp = t.strftime("%Y%m%d")
    canonical_uri = f"/vendor/orders/v1/purchaseOrders/{po_number}"
    canonical_headers = f"host:{host}\nx-amz-access-token:{access_token}\nx-amz-date:{amz_date}\n"
    signed_headers = "host;x-amz-access-token;x-amz-date"
    payload_hash = hashlib.sha256("".encode("utf-8")).hexdigest()
    canonical_request = f"{method}\n{canonical_uri}\n\n{canonical_headers}\n{signed_headers}\n{payload_hash}"
    algorithm = "AWS4-HMAC-SHA256"
    credential_scope = f"{datestamp}/{region}/{service}/aws4_request"
    string_to_sign = f"{algorithm}\n{amz_date}\n{credential_scope}\n{hashlib.sha256(canonical_request.encode('utf-8')).hexdigest()}"
    signing_key = get_signature_key(aws_secret_key, datestamp, region, service)
    signature = hmac.new(signing_key, string_to_sign.encode("utf-8"), hashlib.sha256).hexdigest()
    authorization_header = f"{algorithm} Credential={aws_access_key}/{credential_scope}, SignedHeaders={signed_headers}, Signature={signature}"
    headers = {
        "x-amz-access-token": access_token,
        "x-amz-date": amz_date,
        "Authorization": authorization_header
    }
    response = requests.get(endpoint, headers=headers)
    response.raise_for_status()
    return response.json()["payload"]

#Amazon Data to Cin7 Format
def convert_amz_to_cin7(po_data):
    party_id = po_data["orderDetails"]["shipToParty"]["partyId"]
    address_str = warehouse_address_map.get(party_id, DEFAULT_ADDRESS)
    addr1, addr2, city, state, postcode, country = parse_address(address_str)
    total = sum(float(item["netCost"]["amount"]) * int(item["orderedQuantity"]["amount"]) for item in po_data["orderDetails"]["items"])
    cin7_order = {
        "reference": "",
        "memberId": 6119, #Product
        #"memberId": 5, #Sanbox
        "company": "Amazon Commercial Services Pty Ltd",
        "deliveryAddress1": addr1,
        "deliveryAddress2": addr2,
        "deliveryCity": city,
        "deliveryState": state,
        "deliveryPostalCode": postcode,
        "deliveryCountry": country,
        "billingCompany": "Amazon Commercial Services Pty Ltd",
        "branchId": 6094, #Product
        #"branchId": 4, #sanbox
        "stage": "Release To Pick - WMS",
        "taxStatus": "Excl",
        "taxRate": 0.1,
        "currencyCode": "AUD",
        "total": round(total, 2),
        "productTotal": round(total, 2),
        "source": "Imported",
        "paymentTerms": "NET DUE IN 60 DAYS",
        "customerOrderNo": po_data["purchaseOrderNumber"],
        "lineItems": []
    }
    for item in po_data["orderDetails"]["items"]:
        cin7_order["lineItems"].append({
            "barcode": item["vendorProductIdentifier"],
            "qty": item["orderedQuantity"]["amount"],
            "unitPrice": float(item["netCost"]["amount"]),
            "lineType": "Product"
        })
    return [cin7_order]

#Cin7 Check exist Order
def check_cin7_order_exists(po_number):
    time.sleep(1)  #  Time Sleep for Cin7 API, 429  
    headers = {
        "Authorization": f"Basic {cin7_api_token}"
    }
    params = {
        "customerOrderNo": po_number
    }
    #response = requests.get(cin7_api_url, headers=headers, params=params)
    response = requests.get(cin7_api_url, headers=headers, params=params, timeout=60)

    response.raise_for_status()
    existing_orders = response.json()
    return any(order.get("customerOrderNo") == po_number for order in existing_orders)

#upload to cin7 Sale Order
def upload_to_cin7(order_data):
    headers = {
        "Authorization": f"Basic {cin7_api_token}",
        "Content-Type": "application/json"
    }
    #response = requests.post(cin7_api_url, headers=headers, json=order_data)
    response = requests.post(cin7_api_url, headers=headers, json=order_data, timeout=60)
    response.raise_for_status()
    log(f"Success Upload to Cin7：{response.json()}")


def main():
    try:
        po_numbers = get_amazon_orders_list()
    except Exception as e:
        log(f"Initiallization Failed：{e}")
        error_message = f"Script Init Failed:\n\n{traceback.format_exc()}"
        send_error_email("Init Error: Amazon Orders", error_message)
        return

    log(f"Detected {len(po_numbers)} Uncomfirmed PO，Start upload to Cin7...")
    for po in po_numbers:
        try:
            if check_cin7_order_exists(po):
                log(f"The order exist PO：{po}，Skip upload")
                continue
            po_data = get_amazon_order(po)
            cin7_order = convert_amz_to_cin7(po_data)
            log(f"Onprocess PO：{po}")
            upload_to_cin7(cin7_order)
            time.sleep(upload_delay)
        except Exception as e:
            log(f"Error（PO：{po}）：{type(e).__name__} - {e}")
            error_message = f"PO Error ({po}):\n\n{traceback.format_exc()}"
            send_error_email(f"Error with PO {po}", error_message)

if __name__ == "__main__":
    main()
