"""Exemple exécutable, source synthétique par défaut, maximum une DU par lancement."""
from __future__ import annotations
import hashlib
import json
import os
import sys
import uuid
from pathlib import Path
from urllib.parse import urlsplit
from docoki_sdk import Docoki, DocokiError


def run_tutorial(client: Docoki, *, directory: Path, sample_file: Path, base_url: str, workspace_id: str, webhook_endpoint_id: str | None = None):
    data = sample_file.read_bytes()
    if len(data) > 100 * 1024 * 1024:
        raise DocokiError("INPUT_TOO_LARGE")
    source_hash = hashlib.sha256(data).hexdigest()
    parsed = urlsplit(base_url)
    origin = f"{parsed.scheme}://{parsed.netloc}"
    directory.mkdir(parents=True, exist_ok=True, mode=0o700)
    lock = directory / "run.lock"
    lock_fd = os.open(lock, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600)
    try:
        state_file = directory / "state.json"
        if state_file.exists():
            state = json.loads(state_file.read_text(encoding="utf-8"))
        else:
            state = dict(version=1, api_origin=origin, workspace_id=workspace_id, input_sha256=source_hash, webhook_endpoint_id=webhook_endpoint_id)
        if state.get("webhook_endpoint_id") != webhook_endpoint_id:
            raise DocokiError("CHECKPOINT_TARGET_MISMATCH")
        if state.get("version") != 1 or state.get("api_origin") != origin or state.get("workspace_id") != workspace_id or state.get("input_sha256") != source_hash:
            raise DocokiError("CHECKPOINT_SOURCE_MISMATCH")

        def save():
            temp = state_file.with_suffix(f".{uuid.uuid4()}.tmp")
            with os.fdopen(os.open(temp, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600), "w", encoding="utf-8") as stream:
                json.dump(state, stream, ensure_ascii=False, indent=2)
                stream.write("\n")
            os.replace(temp, state_file)

        if not state.get("upload_id"):
            upload = client.create_upload(file_name="docoki-sdk-synthetique.pdf", declared_mime_type="application/pdf", byte_size=len(data), sha256=source_hash)
            state.update(upload_id=upload["upload_id"], asset_id=upload["asset_id"])
            save()
        if not state.get("estimate_id"):
            client.upload_parts(state["upload_id"], data)
            asset = client.wait_for_asset(state["asset_id"])
            if asset["status"] != "ready":
                raise DocokiError("ASSET_REJECTED")
            quote = client.estimate(dict(operation="organize", input_asset_ids=[state["asset_id"]], parameters={"pages": [{"page": 1, "rotation": 90}]}))
            if not quote["execution_available"]:
                raise DocokiError("OPERATION_UNAVAILABLE")
            if quote["maximum_units"] > 1:
                raise DocokiError("TUTORIAL_BUDGET_EXCEEDED")
            state.update(estimate_id=quote["id"], idempotency_key=str(uuid.uuid4()))
            save()
        request = dict(operation="organize", input_asset_ids=[state["asset_id"]], parameters={"pages": [{"page": 1, "rotation": 90}]}, estimate_id=state["estimate_id"])
        if state.get("webhook_endpoint_id"):
            request["webhook_endpoint_id"] = state["webhook_endpoint_id"]
        # Preserve this exact body/key after an uncertain response; do not create a new launch blindly.
        accepted = client.create_job(request, idempotency_key=state["idempotency_key"])
        job = client.wait_for_job(accepted["id"])
        if job["status"] != "succeeded":
            raise DocokiError("JOB_FAILED" if job["status"] == "failed" else "JOB_CANCELED")
        manifest = client.manifest(job["id"])
        pdf = client.download_asset(job["output_assets"][0]["id"])
        archive = client.download_archive(job["id"])

        def output_file(name, content):
            target = directory / name
            try:
                with os.fdopen(os.open(target, os.O_CREAT | os.O_EXCL | os.O_WRONLY, 0o600), "wb") as stream:
                    stream.write(content)
            except FileExistsError:
                if hashlib.sha256(target.read_bytes()).digest() != hashlib.sha256(content).digest():
                    raise DocokiError("LOCAL_OUTPUT_CONFLICT") from None

        output_file("resultat.pdf", pdf)
        output_file("resultats.zip", archive)
        output_file("manifest.json", (json.dumps(manifest, ensure_ascii=False, indent=2) + "\n").encode("utf-8"))
        return dict(job_id=job["id"], status=job["status"], consumed_units=job["consumed_units"], input_asset_id=state["asset_id"], output_asset_ids=[a["id"] for a in job["output_assets"]], pdf_sha256=hashlib.sha256(pdf).hexdigest())
    finally:
        os.close(lock_fd)
        lock.unlink()


def main():
    try:
        base = os.environ.get("DOCOKI_API_URL", "http://127.0.0.1:4000")
        workspace = os.environ.get("DOCOKI_WORKSPACE_ID", "")
        client = Docoki(base_url=base, api_key=os.environ.get("DOCOKI_API_KEY", ""), workspace_id=workspace, allow_insecure_http=os.environ.get("DOCOKI_ALLOW_HTTP") == "1")
        result = run_tutorial(client, directory=Path(os.environ.get("DOCOKI_RUN_DIR", "docoki-essai-python")), sample_file=Path(os.environ.get("DOCOKI_SAMPLE_FILE", str(Path(__file__).resolve().parent.parent / "sample.pdf"))), base_url=base, workspace_id=workspace, webhook_endpoint_id=os.environ.get("DOCOKI_WEBHOOK_ENDPOINT_ID"))
        print(json.dumps(result))
        return 0
    except Exception as error:
        print(json.dumps(dict(error=error.code if isinstance(error, DocokiError) else "LOCAL_CONFIGURATION_ERROR", request_id=error.request_id if isinstance(error, DocokiError) else None)), file=sys.stderr)
        return 1


if __name__ == "__main__":
    sys.exit(main())
