"""Rhino 8 Python 3 inputs: Run, ApiKey, BaseUrl, Action, ProjectId, ModelId, Name,
Description, OperationId. Outputs: Status, Data, ResultId.
Connect ApiKey, or set ONTOLOGIC_API_KEY before Rhino. Reuse OperationId on retries.
"""
import json
import os
import uuid
from urllib.parse import urlsplit
from urllib.request import Request, build_opener, HTTPRedirectHandler
from urllib.error import HTTPError, URLError


class NoRedirects(HTTPRedirectHandler):
    def redirect_request(self, req, fp, code, msg, headers, newurl):
        return None


def api_request(base, key, action, project_id="", model_id="", name="",
                description="", operation_id=""):
    parsed = urlsplit(base)
    local = parsed.hostname in ("localhost", "127.0.0.1", "::1")
    if parsed.scheme != "https" and not (parsed.scheme == "http" and local):
        raise ValueError("Use HTTPS, or HTTP on localhost for local testing.")
    if not parsed.hostname or parsed.username or parsed.password or parsed.query or parsed.fragment:
        raise ValueError("BaseUrl must be a site URL without credentials, query or fragment.")
    if parsed.path not in ("", "/"):
        raise ValueError("BaseUrl must not include /api/v1 or another path.")
    if not key or any(c.isspace() for c in key):
        raise ValueError("Supply ApiKey, or set ONTOLOGIC_API_KEY before starting Rhino.")
    paths = {"whoami": "/whoami", "list_projects": "/projects"}
    method, body = "GET", None
    headers = {"Authorization": "Bearer " + key, "Accept": "application/json"}
    if action == "create_project":
        if not operation_id or len(operation_id) < 8:
            raise ValueError("Supply a stable OperationId of 8–128 characters.")
        paths[action] = "/projects"
        method = "POST"
        body = json.dumps({"name": name, "description": description}).encode("utf-8")
        headers.update({"Content-Type": "application/json", "Idempotency-Key": operation_id})
    elif action in ("read_project", "list_models", "read_model"):
        project_id = str(uuid.UUID(str(project_id)))
        paths[action] = "/projects/" + project_id
        if action in ("list_models", "read_model"):
            paths[action] += "/models"
        if action == "read_model":
            paths[action] += "/" + str(uuid.UUID(str(model_id)))
    if action not in paths:
        raise ValueError("Choose whoami, list_projects, create_project, read_project, list_models or read_model.")
    request = Request(base.rstrip("/") + "/api/v1" + paths[action],
                      data=body, headers=headers, method=method)
    try:
        response = build_opener(NoRedirects()).open(request, timeout=15)
    except HTTPError as error:
        response = error
    except (URLError, TimeoutError):
        return "Connection failed or timed out. Check BaseUrl and network.", "", ""
    with response:
        status = response.code
        try:
            payload = json.loads(response.read(1024 * 1024).decode("utf-8"))
        except (ValueError, UnicodeError):
            return "HTTP {}: expected JSON; check canonical URL and deployment protection.".format(status), "", ""
        if status < 200 or status >= 300:
            error = payload.get("error", {})
            return "HTTP {}: {} (request {})".format(status, error.get("code", "unexpected_response"),
                                                       error.get("requestId", "unknown")), "", ""
        data = payload.get("data", {})
        replay = response.headers.get("Idempotency-Replayed")
        message = "HTTP {}".format(status) + ("; replayed=" + replay if replay else "")
        return message, json.dumps(data, indent=2, ensure_ascii=False), data.get("id", "")


if "Run" in globals():
    Status, Data, ResultId = "Press Run to send one request.", "", ""
    if Run:
        try:
            Status, Data, ResultId = api_request(
                BaseUrl or "https://www.ontologic.tools",
                (globals().get("ApiKey") or "").strip() or os.environ.get("ONTOLOGIC_API_KEY", "").strip(),
                Action or "whoami", ProjectId, ModelId, Name or "Grasshopper API test",
                Description or "Text sent from Grasshopper", OperationId)
        except ValueError as error:
            Status = str(error)
        except Exception:
            Status = "Request failed. Check inputs and retry with the same OperationId."
