#!/usr/bin/env python3
"""Test CloudTrips APIM v2 with OAuth client credentials; standard library only."""
import getpass
import json
import re
import sys
import urllib.error
import urllib.parse
import urllib.request
import uuid


class NoRedirect(urllib.request.HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None


def request(url, *, data=None, token=None):
    headers = {"Accept": "application/json"}
    if data is not None:
        headers["Content-Type"] = "application/x-www-form-urlencoded"
    if token:
        headers["Authorization"] = "Bearer " + token
    req = urllib.request.Request(url, data=data, headers=headers)
    opener = urllib.request.build_opener(NoRedirect())
    try:
        with opener.open(req, timeout=60) as response:
            return response.status, response.headers, response.read()
    except urllib.error.HTTPError as error:
        return error.code, error.headers, error.read()


def guid(prompt):
    return str(uuid.UUID(input(prompt).strip()))


def main():
    tenant_id = guid("Directory (tenant) ID: ")
    api_id = guid("API app Application (client) ID (ct-apim-api): ")
    client_id = guid("Caller Application (client) ID (ct-apim-client): ")
    if api_id == client_id:
        raise ValueError("Use two different app registrations for API and caller.")
    service = input("APIM resource name (for example apim-ctgateway-weu): ").strip()
    if not re.fullmatch(r"[A-Za-z][A-Za-z0-9-]{0,48}[A-Za-z0-9]", service):
        raise ValueError("Enter the APIM resource name only, not a URL.")
    endpoint = (
        f"https://{service}.azure-api.net/petstore/v2/pet/findByStatus"
        "?status=available"
    )
    for label, token in [("NO TOKEN", None), ("INVALID TOKEN", "not-a-valid-jwt")]:
        status, _, _ = request(endpoint, token=token)
        print(f"{label}: HTTP {status}")
        if status != 401:
            raise RuntimeError(
                "Expected 401. Check the v2 route and saved token-validation policy."
            )

    secret = getpass.getpass("Caller client secret VALUE (hidden): ")
    if not secret:
        raise ValueError("A client secret value is required.")
    form = urllib.parse.urlencode({
        "client_id": client_id,
        "client_secret": secret,
        "scope": f"api://{api_id}/.default",
        "grant_type": "client_credentials",
    }).encode("utf-8")
    print("Requesting an access token from Microsoft Entra ID...")
    status, _, raw = request(
        f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token",
        data=form,
    )
    form = None
    try:
        result = json.loads(raw)
    except (ValueError, UnicodeDecodeError):
        raise RuntimeError(f"Token endpoint returned HTTP {status} without JSON.") from None
    if status != 200:
        # Only show standard error fields; never print the complete token response.
        description = str(result.get("error_description", "No description returned."))
        description = description.replace(secret, "[REDACTED]")
        raise RuntimeError(f"Token request HTTP {status}: {description}")
    secret = None
    access_token = result.get("access_token")
    if not isinstance(access_token, str) or not access_token:
        raise RuntimeError("No access token returned; check the Entra app configuration.")
    result = None
    raw = None
    status, headers, body = request(endpoint, token=access_token)
    access_token = None
    print(f"VALID TOKEN: HTTP {status}")
    if status != 200:
        raise RuntimeError(
            "Expected 200. For 401, check audience, caller ID, Api.Invoke consent, "
            "token version, and Subscription required = Off on v2. "
            "For 400/429, remove the old operation policy. "
            "For 5xx, check backend availability."
        )
    version = headers.get("X-CloudTrips-API-Version", "missing")
    print(f"API version: {version}")
    pets = json.loads(body)
    if version != "v2" or not isinstance(pets, list):
        raise RuntimeError("Expected the v2 response header and a JSON array from Petstore.")
    print(f"Pet records returned: {len(pets)} (shared demo data varies)")
    print("PASS: missing and invalid tokens rejected; authorized call succeeded.")


if __name__ == "__main__":
    try:
        main()
    except (ValueError, RuntimeError, urllib.error.URLError, TimeoutError) as error:
        print(f"ERROR: {error}", file=sys.stderr)
        sys.exit(1)
    except (KeyboardInterrupt, EOFError):
        print("\nCancelled.", file=sys.stderr)
        sys.exit(1)
