"""AruHub video example: Python 3.10+, standard library only.
create REQUEST.json TASK.json OUTPUT.mp4 | resume TASK.json OUTPUT.mp4
Set ARUHUB_API_KEY. POST is never retried automatically.
"""
import json
import os
from pathlib import Path
import re
import sys
import time
import urllib.error
import urllib.parse
import urllib.request

API = "https://api.aruhub.com"

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

OPENER = urllib.request.build_opener(NoRedirect)

def auth_headers(url, key):
    parsed = urllib.parse.urlsplit(url)
    if parsed.scheme != "https" or not parsed.hostname or parsed.username or parsed.password:
        raise ValueError("Only HTTPS URLs without embedded credentials are accepted")
    if (parsed.hostname, parsed.port or 443) == ("api.aruhub.com", 443):
        return {"Authorization": "Bearer " + key}
    return {}

def request_json(path, key, payload=None):
    url = API + path
    data = None if payload is None else json.dumps(payload, ensure_ascii=False).encode("utf-8")
    headers = auth_headers(url, key)
    headers["Accept"] = "application/json"
    if data is not None:
        headers["Content-Type"] = "application/json"
    req = urllib.request.Request(url, data=data, headers=headers)
    with OPENER.open(req, timeout=180 if data is not None else 30) as response:
        return json.load(response)

def save_state(path, state):
    temp = path.with_name(path.name + ".tmp")
    temp.write_text(json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8")
    os.replace(temp, path)

def wait_video(task_id, key, timeout=1800):
    deadline, delay = time.monotonic() + timeout, 5
    while time.monotonic() < deadline:
        try:
            result = request_json("/v1/videos/" + urllib.parse.quote(task_id, safe=""), key)
        except urllib.error.HTTPError as exc:
            if exc.code != 429 and not 500 <= exc.code <= 599:
                raise RuntimeError(f"Query HTTP {exc.code}; check key, task ID and permissions") from None
            retry = exc.headers.get("Retry-After", "")
            delay = min(60, max(delay, int(retry))) if retry.isdigit() else min(30, delay * 2)
            print(f"Query HTTP {exc.code}; retry GET in {delay}s")
        except (urllib.error.URLError, TimeoutError):
            delay = min(30, delay * 2)
            print(f"Temporary query network error; retry GET in {delay}s")
        else:
            status = result.get("status")
            print("Task status:", status, flush=True)
            if status == "completed":
                raw = result.get("video_url") or (result.get("metadata") or {}).get("url")
                if not raw:
                    raise RuntimeError("Completed without a result URL; preserve task and contact support")
                return urllib.parse.urljoin(API + "/", raw)
            if status == "failed":
                error = result.get("error") or {}
                message = error.get("message", "Video generation failed") if isinstance(error, dict) else str(error)
                raise RuntimeError(message)
            if status not in ("queued", "in_progress"):
                raise RuntimeError("Unrecognized task state; preserve task ID and inspect response")
            delay = min(15, delay + 1)
        time.sleep(min(delay, max(0, deadline - time.monotonic())))
    raise TimeoutError("Client wait limit reached; use resume later. Server task is not cancelled.")

def download(url, key, output):
    part = output.with_name(output.name + ".part")
    for _ in range(6):
        try:
            response = OPENER.open(urllib.request.Request(url, headers=auth_headers(url, key)), timeout=300)
        except urllib.error.HTTPError as exc:
            if exc.code in (301, 302, 303, 307, 308) and exc.headers.get("Location"):
                url = urllib.parse.urljoin(url, exc.headers["Location"])
                continue  # Rebuild auth for the next host, never forward it blindly.
            raise RuntimeError(f"Download HTTP {exc.code}; use resume to retry this task") from None
        with response:
            mime = response.headers.get_content_type()
            if response.status not in (200, 206) or not (mime.startswith("video/") or mime == "application/octet-stream"):
                raise RuntimeError("Download is not a successful video response")
            expected = int(response.headers["Content-Length"]) if response.headers.get("Content-Length", "").isdigit() else None
            if response.status == 206:
                match = re.fullmatch(r"bytes (\d+)-(\d+)/(\d+)", response.headers.get("Content-Range", ""))
                if not match or int(match[1]) != 0 or int(match[2]) + 1 != int(match[3]):
                    raise RuntimeError("Partial range only; implement range assembly or use another downloader")
                expected = int(match[3])
            total, first = 0, b""
            with part.open("wb") as target:
                while chunk := response.read(1024 * 1024):
                    if not first:
                        first = chunk[:256]
                    target.write(chunk)
                    total += len(chunk)
            if total == 0 or (expected is not None and total != expected):
                raise RuntimeError("Incomplete download; .part retained, use resume")
            if b"ftyp" not in first[:64]:
                raise RuntimeError("Expected an MP4 container; inspect .part before using it")
        os.replace(part, output)
        print("Video saved:", output.resolve())
        return
    raise RuntimeError("Too many download redirects")

def main(argv):
    if not ((len(argv) == 5 and argv[1] == "create") or (len(argv) == 4 and argv[1] == "resume")):
        raise ValueError("Usage: create REQUEST.json TASK.json OUTPUT.mp4 | resume TASK.json OUTPUT.mp4")
    key = os.environ.get("ARUHUB_API_KEY", "").strip()
    if not key:
        raise ValueError("Set ARUHUB_API_KEY first")
    state_path, output = Path(argv[-2]), Path(argv[-1])
    if output.exists():
        raise FileExistsError("Output already exists; choose a new output filename")
    if argv[1] == "create":
        payload = json.loads(Path(argv[2]).read_text(encoding="utf-8-sig"))
        state = {"state": "creation_uncertain", "model": payload.get("model"), "created_at": int(time.time())}
        # Exclusive reservation prevents duplicate creation and preserves uncertainty after a crash.
        with state_path.open("x", encoding="utf-8") as target:
            json.dump(state, target, ensure_ascii=False, indent=2)
        try:
            result = request_json("/v1/videos", key, payload)
            task_id = result.get("id")
            if not isinstance(task_id, str) or not re.fullmatch(r"[A-Za-z0-9_-]+", task_id):
                raise RuntimeError("Creation response has no valid local id")
            state.update(state="submitted", id=task_id)
            save_state(state_path, state)
        except Exception:
            print("Creation outcome must be checked; reservation retained. Do NOT automatically POST again.", file=sys.stderr)
            raise
    else:
        state = json.loads(state_path.read_text(encoding="utf-8-sig"))
        task_id = state.get("id")
        if not isinstance(task_id, str) or not re.fullmatch(r"[A-Za-z0-9_-]+", task_id):
            raise ValueError("No confirmed task ID; check the original submission before any new create")
    result_url = wait_video(task_id, key)
    download(result_url, key, output)

if __name__ == "__main__":
    try:
        main(sys.argv)
    except Exception as exc:
        # HTTPError / URLError can contain signed URLs; never dump the full request.
        message = f"HTTP {exc.code}" if isinstance(exc, urllib.error.HTTPError) else "Network request failed" if isinstance(exc, urllib.error.URLError) else str(exc)
        print("Error:", message, file=sys.stderr)
        sys.exit(1)
