# Place a call so that retries can never place it twice. Python 3.9+ with requests.
#   LOGICAL_ID=renewal-L-1042-2026-11 AGENT_ID=agent_... python create-call-reliably.py
# Every request runs, so the script never resends blindly: after a timeout or a 5xx it lists the
# calls to that number and looks for its own LOGICAL_ID in metadata before sending again.
import json
import os
import random
import re
import sys
import time
from datetime import datetime, timedelta, timezone

import requests

base = os.environ.get("RELATIONSAI_API_BASE", "https://voice-api.relationsai.com/v1")
logical_id = os.environ.get("LOGICAL_ID", "")
agent_id = os.environ.get("AGENT_ID", "agent_1ZltiWzxrXmMprGD6q63")  # the test-mode Sample agent by default
if not re.fullmatch(r"[A-Za-z0-9._-]{1,100}", logical_id):
    sys.exit("Set LOGICAL_ID to your own id for this call (letters, digits, . _ -)")
auth = {"Authorization": f"Bearer {os.environ['RELATIONSAI_API_KEY']}"}


def backoff(attempt):
    return random.uniform(0, min(30, 2**attempt))  # full jitter, capped


# 1. Save what you are about to do BEFORE sending anything: your id, the time, the request.
directory = os.environ.get("OUTBOX_DIR", "./outbox")
os.makedirs(directory, exist_ok=True)
path = os.path.join(directory, f"{logical_id}.json")
try:
    with open(path) as f:
        record = json.load(f)
except FileNotFoundError:
    record = {
        "created_at": datetime.now(timezone.utc).isoformat(timespec="milliseconds").replace("+00:00", "Z"),
        "request": {"agent_id": agent_id, "to": os.environ.get("TO", "+12025550100"), "metadata": {"logical_id": logical_id}},
    }
    with open(path, "x") as f:
        json.dump(record, f, indent=2)


def done(call, how):
    with open(path, "w") as f:
        json.dump({**record, "call_id": call["id"]}, f, indent=2)
    print(f"{call['id']} status={call['status']}{how}")
    sys.exit(0)


if record.get("call_id"):
    print(f"{record['call_id']}: already placed for {logical_id} (from the outbox), not calling again")
    sys.exit(0)


def find_existing_call():
    """The call with our LOGICAL_ID, None if there is none, False if the list cannot be read."""
    created = datetime.fromisoformat(record["created_at"].replace("Z", "+00:00"))
    since = (created - timedelta(minutes=10)).isoformat(timespec="milliseconds").replace("+00:00", "Z")
    r = requests.get(f"{base}/calls", params={"to": record["request"]["to"], "created_after": since, "limit": 100}, headers=auth, timeout=30)
    if not r.ok:
        return False
    return next((c for c in r.json()["data"] if c["metadata"].get("logical_id") == logical_id), None)


# 2. Send; after any answer that may hide a placed call, check before sending again.
must_check = False
for attempt in range(1, 7):
    if must_check:
        existing = find_existing_call()
        if existing:
            done(existing, " (found: the earlier attempt placed it)")
        if existing is False:
            sys.exit("Could not read the call list; run again later with the same LOGICAL_ID.")
    try:
        r = requests.post(f"{base}/calls", headers=auth, json=record["request"], timeout=30)
    except requests.RequestException as err:
        print(f"attempt {attempt}: {type(err).__name__}; checking the call list before sending again")
        must_check = True
        time.sleep(2 + backoff(attempt))  # give an accepted call a moment to appear in the list
        continue
    body = r.json()
    if r.ok:
        done(body, "")
    err = body["error"]
    if err["retry"] == "no":
        # Refused before anything was dialled. Fix the cause; do not retry this request as is.
        sys.exit(f"{r.status_code} {err['code']}: {err['message']} (request {err['request_id']})")
    # 429 and 503 mean nothing was done; a 500 (retry "yes") may hide a placed call.
    must_check = r.status_code >= 500 and r.status_code != 503
    wait = float(r.headers.get("Retry-After", "1")) if err["retry"] == "after" else 2 + backoff(attempt)
    if wait > 120:
        sys.exit(f"{err['code']}: try again after {wait:.0f}s with the same LOGICAL_ID")
    print(f"attempt {attempt}: {r.status_code} {err['code']}; " + ("checking before sending again" if must_check else f"retrying in {wait:.1f}s"))
    time.sleep(wait)

sys.exit("Still unconfirmed. Run again later with the same LOGICAL_ID; it checks before sending.")
