#!/usr/bin/env python3
"""Update a contact without overwriting a change you have not seen.

Read it (ETag), compute the change from what you read, PATCH with If-Match, and on 412 start over.
Requires Python 3.9+ and requests. Runs in test mode only: it creates a test-mode contact for
+12025550100.

    export RELATIONSAI_API_KEY="rai_test_..."
    python3 etag-update.py
"""
import os
import sys

import requests

api_key = os.environ.get("RELATIONSAI_API_KEY", "")
base_url = os.environ.get("RELATIONSAI_API_BASE", "https://voice-api.relationsai.com/v1").rstrip("/")
session = requests.Session()
session.headers["Authorization"] = f"Bearer {api_key}"


def failure(r):
    e = r.json()["error"]
    return RuntimeError(f"{r.status_code} {e['code']} (request {e['request_id']}): {e['message']}")


def update_contact(contact_id, change, max_attempts=3):
    """Read-modify-write. `change` gets the current contact and returns the fields to PATCH, or None if
    nothing needs to change. It runs again on every attempt, against the version just read."""
    for _ in range(max_attempts):
        read = session.get(f"{base_url}/contacts/{contact_id}", timeout=30)
        if not read.ok:
            raise failure(read)
        etag = read.headers.get("ETag")
        if not etag:
            raise RuntimeError("The response has no ETag header.")

        patch = change(read.json())
        if patch is None:
            return read.json()

        # Send the ETag exactly as received, quotes included.
        write = session.patch(f"{base_url}/contacts/{contact_id}", json=patch, headers={"If-Match": etag}, timeout=30)
        if write.ok:
            return write.json()
        if write.json()["error"]["code"] != "version_conflict":
            raise failure(write)
        print("The contact changed since it was read: reading it again")
    raise RuntimeError(f"Gave up after {max_attempts} conflicting updates.")


def main():
    if not api_key.startswith("rai_test_"):
        raise RuntimeError("Set RELATIONSAI_API_KEY to a test key (rai_test_...).")

    # Make sure the test contact exists (create or update by phone number).
    r = session.post(
        f"{base_url}/contacts/upsert",
        json={"phone_number": "+12025550100", "name": "asha rao"},
        timeout=30,
    )
    if not r.ok:
        raise failure(r)
    contact = r.json()
    print(f"Contact {contact['id']}: name \"{contact['name']}\"")

    # Capitalise the name, based on the stored value rather than on a copy you read earlier.
    def capitalise(current):
        name = (current["name"] or "").title()
        return None if name == current["name"] else {"name": name}

    updated = update_contact(contact["id"], capitalise)
    print(f"Updated: name \"{updated['name']}\"")


if __name__ == "__main__":
    try:
        main()
    except (RuntimeError, requests.RequestException) as err:
        sys.exit(f"Error: {err}")
