envelope = f"""<?xml version="1.0" encoding="utf-8"?>
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:xsd="http://www.w3.org/2001/XMLSchema">
  <s:Header>
    <ApplicationToken xmlns="http://www.superoffice.com/superid/partnersystemuser/0.1">{APPLICATION_TOKEN}</ApplicationToken>
    <ContextIdentifier xmlns="http://www.superoffice.com/superid/partnersystemuser/0.1">{CONTEXT_IDENTIFIER}</ContextIdentifier>
  </s:Header>
  <s:Body>
    <AuthenticationRequest xmlns="http://www.superoffice.com/superid/partnersystemuser/0.1">
      <SignedSystemToken>{SIGNED_SYSTEM_TOKEN}</SignedSystemToken>
      <ReturnTokenType>{RETURN_TOKEN_TYPE}</ReturnTokenType>
    </AuthenticationRequest>
  </s:Body>
</s:Envelope>
""".strip()

headers = {
    "Content-Type": "text/xml; charset=utf-8",
    "SOAPAction": "http://www.superoffice.com/superid/partnersystemuser/0.1/IPartnerSystemUserService/Authenticate",
}

resp = requests.post(
    SOAP_URL, data=envelope.encode("utf-8"), headers=headers, timeout=30
)
print(resp)
# --- Useful diagnostics if server responds with HTML or error ---
ct = resp.headers.get("Content-Type", "")
if "xml" not in ct.lower():
    print("Unexpected response (not XML). Status:", resp.status_code)
    print("Content-Type:", ct)
    print(resp.text[:1200])
    raise SystemExit("Check URL, SOAPAction, or required SOAP headers/values.")

# --- Parse SOAP response per WSDL ---
root = ET.fromstring(resp.text)
ns = {
    "s": "http://schemas.xmlsoap.org/soap/envelope/",
    "tns": "http://www.superoffice.com/superid/partnersystemuser/0.1",
}

# Fault?
fault = root.find(".//s:Fault", ns)
if fault is not None:
    print(resp.text)
    raise SystemExit("SOAP Fault returned. See XML above.")

# Extract AuthenticationResponse
is_ok_el = root.find(".//tns:AuthenticationResponse/tns:IsSuccessful", ns)
token_el = root.find(".//tns:AuthenticationResponse/tns:Token", ns)
err_el = root.find(".//tns:AuthenticationResponse/tns:ErrorMessage", ns)

is_ok = is_ok_el is not None and (is_ok_el.text or "").strip().lower() == "true"
token = token_el.text.strip() if token_el is not None and token_el.text else None
error_msg = (err_el.text or "").strip() if err_el is not None else ""

if not is_ok:
    print("Authenticate returned IsSuccessful = false")
    print("ErrorMessage:", error_msg)
    print(resp.text)
    raise SystemExit("Authentication failed.")

print("Authenticate succeeded.")
print("Token (truncated):", (token[:50] + "...") if token else None)

# If ReturnTokenType=Jwt, you can inspect claims to find the SOTicket claim key
if RETURN_TOKEN_TYPE.lower() == "jwt" and token and "." in token:

    def b64url_decode(seg: str) -> bytes:
        seg += "=" * ((4 - len(seg) % 4) % 4)
        return base64.urlsafe_b64decode(seg.encode("ascii"))

    header_b64, payload_b64, sig_b64 = token.split(".", 2)
    payload = json.loads(b64url_decode(payload_b64))
    print("JWT payload keys:", list(payload.keys()))

    # Try to locate a ticket-like claim (key name may vary by env)
    ticket = None
    for k, v in payload.items():
        if isinstance(v, str) and (
            "ticket" in k.lower() or v.startswith(("7T:", "8A:", "8C:"))
        ):
            ticket = v
            break

    if ticket:
        print("Extracted system-user ticket:", ticket)
        # Example REST call using SOTicket + SO-AppToken headers:
        TENANT_BASE = (
            "https://online.superoffice.com/Cust26703"  # use your actual sodN host!
        )
        rest_headers = {
            "Authorization": f"SOTicket {ticket}",
            "SO-AppToken": APPLICATION_TOKEN,  # same value as ApplicationToken
            "Accept": "application/json",
        }
        test = requests.get(
            f"{TENANT_BASE}/api/v1/contact/1", headers=rest_headers, timeout=30
        )
        print("REST test:", test.status_code, test.text)
    else:
        print(
            "No obvious 'ticket' claim found in JWT. Inspect payload above and pick the correct claim manually."
        )
else:
    print(
        "Returned token is not a JWT (or you requested SAML). Use the token as intended by your flow."
    )
 