{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Setup\n",
    "# Install the required package\n",
    "# %pip install requests dotenv\n",
    "# %pip install dotenv\n",
    "# %pip install --upgrade ShopifyAPI\n",
    "\n",
    "# Import necessary libraries\n",
    "from klaviyo_api import KlaviyoAPI\n",
    "from dotenv import dotenv_values\n",
    "from datetime import datetime, timedelta\n",
    "import time\n",
    "import requests\n",
    "import json\n",
    "import pprint\n",
    "import imp\n",
    "import api\n",
    "imp.reload(api)\n",
    "from api import BigApi\n",
    "\n",
    "BigApi.init(\"../.env\")\n",
    "# BigApi.adminLogin()  # only needed if accessing Bigscreen backend\n",
    "\n",
    "config = dotenv_values(\"../.env\")\n",
    "klaviyoApiKey = config['KLAVIYO_API_KEY']\n",
    "\n",
    "klaviyo = KlaviyoAPI(klaviyoApiKey, max_delay=60, max_retries=3, test_host=None)\n",
    "\n",
    "klaviyo_skip_profile_ids = []\n",
    "incrementing_page_size = 0\n",
    "\n",
    "\n",
    "# Get metric_id for \"Fulfilled Order\"\n",
    "metrics_response = klaviyo.Metrics.get_metrics()\n",
    "for metric in metrics_response.data:\n",
    "    if metric.attributes.name == 'Fulfilled Order':\n",
    "        fulfilled_order_metric_id = metric.id\n",
    "    if metric.attributes.name == 'Fulfilled Partial Order':\n",
    "        fulfilled_partial_order_metric_id = metric.id\n",
    "    if 'fulfilled_order_metric_id' in locals() and 'fulfilled_partial_order_metric_id' in locals():\n",
    "        break\n",
    "else:\n",
    "    raise ValueError(\"Metrics 'Fulfilled Order' and 'Fulfilled Partial Order' not found\")\n",
    "\n",
    "\n",
    "def pprint_klaviyo(response, propsFilter=[]):\n",
    "  # iterate through the properties of response.data[0] and print them\n",
    "  for key, value in response.data[0]:\n",
    "      # for the 'relationships' property, just print \"[...]\" to avoid printing too much\n",
    "      # for the 'attributes' property, print the key-value pairs\n",
    "      if key == 'relationships':\n",
    "          print(f\"{key}: [...]\")\n",
    "      elif key == 'attributes':\n",
    "          print(f\"{key}:\")\n",
    "          for attr_key, attr_value in value:\n",
    "              # for the 'properties' attribute, print the key-value pairs\n",
    "              if attr_key == 'properties':\n",
    "                  print(f\"  {attr_key}:\")\n",
    "                  for prop_key, prop_value in attr_value.items():\n",
    "                      if(not len(propsFilter) or prop_key in propsFilter):\n",
    "                          # for specific properties, print the key-value pair\n",
    "                          if isinstance(prop_value, dict):\n",
    "                              # if the property value is a dictionary, print it in a formatted way\n",
    "                              print(f\"    {prop_key}: {json.dumps(prop_value, indent=2)}\")\n",
    "                          else:\n",
    "                              # otherwise, print the key-value pair directly\n",
    "                              print(f\"    {prop_key}: {prop_value}\")\n",
    "              else:\n",
    "                  # for other attributes, print the key-value pair directly\n",
    "                  print(f\"  {attr_key}: {attr_value}\")\n",
    "      else:\n",
    "          # for other properties, print the key-value pair directly\n",
    "          print(f\"{key}: {value}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# test eyetracking api\n",
    "data = BigApi.etApiGet(f\"/get_user_files\")\n",
    "\n",
    "data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Test Klaviyo API\n",
    "brandon_test_seg = 'RyVhYy'\n",
    "bs2e_ff_no_w1_seg = 'UgHVis'\n",
    "# fields_profile = ['properties.et_token', 'properties.token_wave']\n",
    "fields_profile = None\n",
    "\n",
    "response = klaviyo.Segments.get_profiles_for_segment(brandon_test_seg, fields_profile=fields_profile, page_size=100)\n",
    "# pprint.pprint(response.data, indent=2)\n",
    "\n",
    "if(fields_profile != None and len(fields_profile) > 0):\n",
    "  # Print the size of response.data\n",
    "  print(f\"Number of profiles in segment {brandon_test_seg}: {len(response['data'])}\")\n",
    "  # clear out relationships\n",
    "  for profile in response['data']:\n",
    "      profile.pop('relationships', None)\n",
    "  print(json.dumps(response['data'][0], indent=2))\n",
    "else:\n",
    "  # Print the size of response.data\n",
    "  print(f\"Number of profiles in segment {brandon_test_seg}: {len(response.data)}\")\n",
    "  pprint_klaviyo(response)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Force segment to update\n",
    "brandon_test_seg = 'RyVhYy'\n",
    "bs2e_ff_no_w1_seg = 'UgHVis'\n",
    "target_seg = brandon_test_seg\n",
    "fields_profile = ['properties']\n",
    "# fields_profile = None\n",
    "profiles_list = []\n",
    "\n",
    "\n",
    "update_segment_body = {\n",
    "    'data': {\n",
    "        'type': 'segment',\n",
    "        'id': target_seg,\n",
    "        'attributes': {\n",
    "            'condition_groups': [\n",
    "                {\n",
    "                    'conditions': [{'type': 'profile-property', 'property': 'email', 'filter': {'type': 'string', 'operator': 'starts with', 'value': 'brandon@bigscreenvr.com'}}]\n",
    "                }\n",
    "            ]\n",
    "        }\n",
    "    }\n",
    "}\n",
    "\n",
    "response = klaviyo.Segments.update_segment(target_seg, update_segment_body)\n",
    "print(f\"Segment {target_seg} update response: \\n{response}\")\n",
    "\n",
    "\n",
    "ready = False\n",
    "\n",
    "while not ready:\n",
    "    # Check if the segment is processing\n",
    "    response = klaviyo.Segments.get_segment(target_seg)\n",
    "    if response.data.attributes.is_processing:\n",
    "        print(f\"Segment {target_seg} is still processing...\")\n",
    "        time.sleep(5)  # Wait for 5 seconds before checking again\n",
    "    else:\n",
    "        print(f\"Segment {target_seg} is ready!\")\n",
    "        ready = True\n",
    "\n",
    "\n",
    "# response = klaviyo.Segments.get_profiles_for_segment(bs2eBuyersSeg, fields_profile=fields_profile, page_size=100)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Assign ET tokens to profiles in the segment\n",
    "TOKEN_WAVE_NUMBER = 1 # Set to -1 for debug cases\n",
    "TOKEN_EXPIRATION_DATE = (datetime.now() + timedelta(days=90)).strftime(\"%Y-%m-%d\")\n",
    "PAGE_SIZE_INCREMENT = 5\n",
    "EVENT_CHECK_INTERVAL = 0.5  # seconds\n",
    "FORCE_ASSIGN_ALL = False # Assign tokens to all profiles in the segment, regardless BS2E order status\n",
    "brandon_test_seg = 'RyVhYy'\n",
    "bs2e_ff_missing_token_seg = 'UgHVis'\n",
    "target_seg = bs2e_ff_missing_token_seg\n",
    "fields_profile = ['properties']\n",
    "# fields_profile = None\n",
    "profiles_list = []\n",
    "\n",
    "\n",
    "# NOTE: To circumvent annoying API stalls, we will use a batch size of 5 profiles at a time\n",
    "incrementing_page_size += PAGE_SIZE_INCREMENT\n",
    "print(f\"Getting {incrementing_page_size} profiles from the target segment\")\n",
    "response = klaviyo.Segments.get_profiles_for_segment(target_seg, fields_profile=fields_profile, page_size=incrementing_page_size)\n",
    "# pprint.pprint(response.data, indent=2)\n",
    "\n",
    "if(fields_profile != None and len(fields_profile) > 0):\n",
    "  profiles_list = response['data']\n",
    "else:\n",
    "  profiles_list = response.data\n",
    "\n",
    "# profiles_list = [profiles_list[0]]  # debug\n",
    "\n",
    "\n",
    "# Filter profiles\n",
    "filtered_profile_ids = []\n",
    "filtered_customer_ids = []\n",
    "skip_count = 0\n",
    "for index, profile in enumerate(profiles_list):\n",
    "    profile_id = profile['id']\n",
    "    customer_id = profile_id\n",
    "    if profile_id in klaviyo_skip_profile_ids:\n",
    "        skip_count += 1\n",
    "        # print(f\"Skipping profile {index+1} as it is in the skip list.\")  # debug\n",
    "        continue\n",
    "    # print(\"\\n\")  # debug\n",
    "    all_events = []\n",
    "    # print(f\"Checking profile {index} ({profile_id}) fulfilled orders...\")  # debug\n",
    "    events_response_full = klaviyo.Events.get_events(fields_event=['event_properties'],filter=f'equals(profile_id,\"{profile_id}\"),equals(metric_id,\"{fulfilled_order_metric_id}\")')\n",
    "    # pprint.pprint(events_response_full)  #debug\n",
    "    # print(f\"Checking profile {index} ({profile_id}) fulfilled partial orders...\")  # debug\n",
    "    events_response_partial = klaviyo.Events.get_events(fields_event=['event_properties'],filter=f'equals(profile_id,\"{profile_id}\"),equals(metric_id,\"{fulfilled_partial_order_metric_id}\")')\n",
    "    if len(events_response_full['data']) == 0 and len(events_response_partial['data']) == 0:\n",
    "        print(f\"Profile {profile_id} has no fulfilled orders. Skipping...\")\n",
    "        continue  # Skip profiles with no fulfilled orders\n",
    "    if len(events_response_full['data']) > 0:\n",
    "        all_events.extend(events_response_full['data'])\n",
    "    if len(events_response_partial['data']) > 0:\n",
    "        all_events.extend(events_response_partial['data'])\n",
    "\n",
    "    if not FORCE_ASSIGN_ALL:\n",
    "        print(f\"Filter checking profile {index+1} of {len(profiles_list)}\")  # debug\n",
    "        for event in all_events:\n",
    "            event_properties = event['attributes']['event_properties']\n",
    "            if '$extra' in event_properties and 'line_items' in event_properties['$extra']:\n",
    "                line_items = event_properties['$extra']['line_items']\n",
    "                # pprint.pprint(line_items, indent=2)  # debug\n",
    "                # print(\"\\n\")  # debug\n",
    "                for item in line_items:\n",
    "                    if 'variant_title' in item and item['variant_title'] != None and 'Beyond 2e' in item['variant_title']:\n",
    "                        customer_id = f\"{all_events[0]['attributes']['event_properties']['$extra']['customer']['id']}\"\n",
    "                        filtered_profile_ids.append(profile_id)\n",
    "                        filtered_customer_ids.append(customer_id)\n",
    "                        break\n",
    "                if profile_id in filtered_profile_ids:\n",
    "                    break  # No need to check more events\n",
    "    else:\n",
    "        if index == 0:\n",
    "            print(f\"Force flag is on. Generating ET tokens for all {len(profiles_list)} profile(s)\")\n",
    "        if len(all_events) > 0:\n",
    "            customer_id = f\"{all_events[0]['attributes']['event_properties']['$extra']['customer']['id']}\"\n",
    "        filtered_profile_ids.append(profile_id)\n",
    "        filtered_customer_ids.append(customer_id)\n",
    "    if profile_id not in filtered_profile_ids:\n",
    "        klaviyo_skip_profile_ids.append(profile_id)  # Add profile to skip list to avoid processing it again\n",
    "    # wait 0.5 sec to avoid hitting rate limits\n",
    "    time.sleep(EVENT_CHECK_INTERVAL)\n",
    "\n",
    "print(f\"--- FILTERING DONE. Skipped {skip_count} profiles ---\")\n",
    "print(f\"Number of eligible profiles: {len(filtered_customer_ids)}\")\n",
    "pprint.pprint(filtered_customer_ids)\n",
    "# raise KeyboardInterrupt  # debug\n",
    "\n",
    "\n",
    "if len(filtered_customer_ids) == 0:\n",
    "    raise ValueError(\"No profiles found for ET token assignment\")\n",
    "data = BigApi.etApiPostJson(\"/generate_token_batch\", filtered_profile_ids)\n",
    "# data\n",
    "\n",
    "profile_upserts = []\n",
    "# iterate through data.tokens to create a new list entry for each token. each entry should be an empty dict.\n",
    "for token in data['tokens']:\n",
    "    # store the index from the filtered_customer_ids list\n",
    "    customer_index = filtered_profile_ids.index(token['username'])\n",
    "    profile_id = filtered_profile_ids[customer_index]\n",
    "    klaviyo_skip_profile_ids.append(profile_id)  # Add profile to skip list to avoid processing it again\n",
    "    profile_upserts.append({\n",
    "        'type': 'profile',\n",
    "        'id': profile_id,\n",
    "        'attributes': {\n",
    "            'properties': {\n",
    "                'et_token': token['token'],\n",
    "                'et_token_expires': f\"{TOKEN_EXPIRATION_DATE}\",\n",
    "                'token_wave': f\"{TOKEN_WAVE_NUMBER}\"\n",
    "            }\n",
    "        }\n",
    "    })\n",
    "update_profiles_body = {\n",
    "    'data': {\n",
    "        'type': 'profile-bulk-import-job',\n",
    "        'attributes': {\n",
    "          'profiles': {\n",
    "            'data': profile_upserts\n",
    "          }\n",
    "        }\n",
    "    }\n",
    "}\n",
    "\n",
    "response = klaviyo.Profiles.bulk_import_profiles(update_profiles_body)\n",
    "response"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Check status of bulk profile import job\n",
    "job_id = \"VkFENDhOX21haW50ZW5hbmNlLjVqaHVVSC4xNzU0MzcxODc4LkhzZjByZg\"\n",
    "\n",
    "def get_bulk_import_status(job_id):\n",
    "    try:\n",
    "        # Get the bulk profile import job status\n",
    "        response = klaviyo.Profiles.get_bulk_import_profiles_job(job_id)\n",
    "        \n",
    "        # Extract relevant information from the response\n",
    "        job_data = response.data\n",
    "        attributes = job_data.attributes\n",
    "        # pprint.pprint(response, indent=2)\n",
    "        \n",
    "        # Print job status and relevant details\n",
    "        print(f\"Job ID: {job_data.id}\")\n",
    "        print(f\"Status: {attributes.status}\")\n",
    "        print(f\"Created At: {attributes.created_at}\")\n",
    "        # print(f\"Updated At: {attributes.updated_at}\")\n",
    "        print(f\"Total Profiles: {attributes.total_count}\")\n",
    "        print(f\"Completed Profiles: {attributes.completed_count}\")\n",
    "        print(f\"Failed Profiles: {attributes.failed_count}\")\n",
    "        \n",
    "        return response\n",
    "    \n",
    "    except Exception as e:\n",
    "        print(f\"Error retrieving job status: {str(e)}\")\n",
    "        return None\n",
    "\n",
    "get_bulk_import_status(job_id)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# misc python test\n",
    "# bla = [4, 8, 12]\n",
    "\n",
    "# for i, val in enumerate(bla):\n",
    "#     print(f\"Index: {i}, Value: {val}\")\n",
    "\n",
    "# output current date in format \"YYYY-MM-DD\"\n",
    "from datetime import datetime\n",
    "# set new_date to be 90 days from today\n",
    "new_date = (datetime.now() + timedelta(days=90)).strftime(\"%Y-%m-%d\")\n",
    "print(f\"Current date: {new_date}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Check effectiveness of \"fulfilled BS2E order\" filtering\n",
    "brandon_test_seg = 'RyVhYy'\n",
    "bs2e_ff_no_w1_seg = 'UgHVis'\n",
    "fields_profile = ['properties']\n",
    "# fields_profile = None\n",
    "profiles_list = []\n",
    "\n",
    "# Get metric_id for \"Fulfilled Order\"\n",
    "metrics_response = klaviyo.Metrics.get_metrics()\n",
    "for metric in metrics_response.data:\n",
    "    if metric.attributes.name == 'Fulfilled Order':\n",
    "        fulfilled_order_metric_id = metric.id\n",
    "        break\n",
    "else:\n",
    "    raise ValueError(\"Metric 'Fulfilled Order' not found\")\n",
    "\n",
    "\n",
    "response = klaviyo.Segments.get_profiles_for_segment(bs2e_ff_no_w1_seg, fields_profile=fields_profile, page_size=100)\n",
    "# pprint.pprint(response.data, indent=2)\n",
    "\n",
    "if(fields_profile != None and len(fields_profile) > 0):\n",
    "  profiles_list = response['data']\n",
    "else:\n",
    "  profiles_list = response.data\n",
    "\n",
    "# profiles_list = [profiles_list[0]]  # debug\n",
    "\n",
    "# Filter profiles\n",
    "filtered_profile_ids = []\n",
    "for index, profile in enumerate(profiles_list):\n",
    "    # print(f\"Filter checking profile at index {index}\")  # debug\n",
    "    # print(\"\\n\")  # debug\n",
    "    profile_id = profile['id']\n",
    "    events_response = klaviyo.Events.get_events(filter=f'equals(profile_id,\"{profile_id}\"),equals(metric_id,\"{fulfilled_order_metric_id}\")')\n",
    "    for event in events_response.data:\n",
    "        event_properties = event.attributes.event_properties\n",
    "        if '$extra' in event_properties and 'line_items' in event_properties['$extra']:\n",
    "            line_items = event_properties['$extra']['line_items']\n",
    "            # pprint.pprint(line_items, indent=2)  # debug\n",
    "            # print(\"\\n\")  # debug\n",
    "            for item in line_items:\n",
    "                if 'variant_title' in item and item['variant_title'] != None and 'Beyond 2e' in item['variant_title']:\n",
    "                    filtered_profile_ids.append(profile_id)\n",
    "                    break\n",
    "            if profile_id in filtered_profile_ids:\n",
    "                break  # No need to check more events\n",
    "\n",
    "print(f\"Number of eligible profiles: {len(filtered_profile_ids)}\")\n",
    "filtered_profile_ids"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Test batch profile import without profile IDs (matching on email works yep yep wahoo)\n",
    "\n",
    "profile_upserts = []\n",
    "profile_upserts.append({\n",
    "    'type': 'profile',\n",
    "    # 'id': profile_id,\n",
    "    'attributes': {\n",
    "        'email': 'brandon+test1@bigscreenvr.com',\n",
    "        'properties': {\n",
    "            'et_token': \"shoogies\"\n",
    "        }\n",
    "    }\n",
    "})\n",
    "update_profiles_body = {\n",
    "    'data': {\n",
    "        'type': 'profile-bulk-import-job',\n",
    "        'attributes': {\n",
    "          'profiles': {\n",
    "            'data': profile_upserts\n",
    "          }\n",
    "        }\n",
    "    }\n",
    "}\n",
    "\n",
    "response = klaviyo.Profiles.bulk_import_profiles(update_profiles_body)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Test singular profile update without profile ID\n",
    "\n",
    "update_profile_body = {\n",
    "  'data': {\n",
    "    'type': 'profile',\n",
    "    'attributes': {\n",
    "      'email': 'dtupper@gmail.com',\n",
    "      'properties': {\n",
    "        'et_token': 'double_shoogies',\n",
    "      }\n",
    "    }\n",
    "  }\n",
    "}\n",
    "\n",
    "response = klaviyo.Profiles.create_or_update_profile(update_profile_body)\n",
    "response"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.11"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
