#!/usr/bin/env python3
from __future__ import annotations

import argparse
import json
import re
import urllib.request
from pathlib import Path
from typing import Any
from urllib.error import HTTPError, URLError


DEFAULT_BASE_URL = "http://docker.cin.su:8011"
DEFAULT_MCP_URL = "http://docker.cin.su:8021"
EXPECTED_CONTRACT_VERSION = "onec-selector-contract.v1"
PREFLIGHT_CLASSIFICATION_STATUSES = {
    "ready",
    "needs_prepare",
    "needs_resolution",
    "needs_repository_lock",
    "blocked",
    "blocked_by_support",
    "blocked_by_support_live_sql",
    "blocked_by_support_rule",
    "blocked_support_unknown",
}
EXTENSION_GUID_LAYER_RE = re.compile(
    r"^extension:[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
    re.IGNORECASE,
)


def post_json(url: str, payload: dict[str, Any], *, timeout: float, headers: dict[str, str] | None = None) -> tuple[dict[str, str], dict[str, Any]]:
    request = urllib.request.Request(
        url,
        data=json.dumps(payload, ensure_ascii=False).encode("utf-8"),
        method="POST",
        headers={"Content-Type": "application/json", **(headers or {})},
    )
    with urllib.request.urlopen(request, timeout=timeout) as response:
        raw = response.read().decode("utf-8")
        data = json.loads(raw) if raw else {}
        return dict(response.headers), data if isinstance(data, dict) else {"status": "error", "error": "response_not_object", "response": data}


def rest_rpc(base_url: str, method: str, payload: dict[str, Any], timeout: float) -> dict[str, Any]:
    _, data = post_json(
        base_url.rstrip("/") + "/rpc",
        {"method": method, "payload": payload},
        timeout=timeout,
        headers={"Content-Type": "application/json; charset=utf-8"},
    )
    return data


def mcp_initialize(mcp_url: str, timeout: float) -> tuple[str | None, dict[str, Any]]:
    body = {
        "jsonrpc": "2.0",
        "id": "write-preflight-initialize",
        "method": "initialize",
        "params": {
            "protocolVersion": "2025-06-18",
            "capabilities": {},
            "clientInfo": {"name": "smoke_1c_write_preflight", "version": "1"},
        },
    }
    try:
        headers, data = post_json(
            mcp_url.rstrip("/") + "/mcp",
            body,
            timeout=timeout,
            headers={"Accept": "application/json, text/event-stream"},
        )
    except HTTPError as exc:
        return None, {"status": "error", "error": "mcp_initialize_http_error", "http_status": exc.code, "response": exc.read().decode("utf-8", errors="replace")}
    except URLError as exc:
        return None, {"status": "error", "error": "mcp_initialize_url_error", "message": str(exc)}
    if "error" in data:
        return None, {"status": "error", "error": "mcp_initialize_jsonrpc_error", "details": data.get("error")}
    return headers.get("Mcp-Session-Id"), data


def mcp_rpc(mcp_url: str, method: str, payload: dict[str, Any], timeout: float, session_id: str | None) -> dict[str, Any]:
    headers = {"Accept": "application/json, text/event-stream"}
    if session_id:
        headers["Mcp-Session-Id"] = session_id
    body = {
        "jsonrpc": "2.0",
        "id": f"write-preflight-{method}",
        "method": "tools/call",
        "params": {"name": "onec_request", "arguments": {"method": method, "payload": payload}},
    }
    try:
        _, data = post_json(mcp_url.rstrip("/") + "/mcp", body, timeout=timeout, headers=headers)
    except HTTPError as exc:
        return {"status": "error", "error": "mcp_http_error", "http_status": exc.code, "response": exc.read().decode("utf-8", errors="replace")}
    except URLError as exc:
        return {"status": "error", "error": "mcp_url_error", "message": str(exc)}
    if "error" in data:
        return {"status": "error", "error": "mcp_jsonrpc_error", "details": data.get("error")}
    result = data.get("result") if isinstance(data.get("result"), dict) else {}
    content = result.get("content") if isinstance(result.get("content"), list) else []
    first = content[0] if content and isinstance(content[0], dict) else {}
    text = first.get("text")
    if not isinstance(text, str):
        return {"status": "error", "error": "mcp_tool_text_missing", "result": result}
    try:
        decoded = json.loads(text)
    except json.JSONDecodeError as exc:
        return {"status": "error", "error": "mcp_tool_text_not_json", "message": str(exc), "text": text[:500]}
    return decoded if isinstance(decoded, dict) else {"status": "error", "error": "mcp_tool_payload_not_object", "payload": decoded}


def rpc_call(endpoint_url: str, method: str, payload: dict[str, Any], timeout: float, *, transport: str, session_id: str | None) -> dict[str, Any]:
    if transport == "mcp":
        return mcp_rpc(endpoint_url, method, payload, timeout, session_id)
    return rest_rpc(endpoint_url, method, payload, timeout)


def require(condition: bool, message: str, failures: list[str]) -> None:
    if not condition:
        failures.append(message)


def classified_preflight_status(value: Any) -> bool:
    return str(value or "") in PREFLIGHT_CLASSIFICATION_STATUSES


def method_names(help_result: dict[str, Any]) -> set[str]:
    return {str(item.get("name") or "") for item in help_result.get("methods") or [] if isinstance(item, dict)}


def first_saved_module_target(endpoint_url: str, base_id: str, timeout: float, *, transport: str, session_id: str | None) -> dict[str, Any] | None:
    result = rpc_call(
        endpoint_url,
        "metadata.saved_state.modules.search",
        {"base_id": base_id, "tables": ["ConfigCASSave", "ConfigSave"], "limit": 5, "scan_limit": 500, "timeout_seconds": int(timeout)},
        timeout,
        transport=transport,
        session_id=session_id,
    )
    modules = result.get("modules") if isinstance(result.get("modules"), list) else []
    targets: list[dict[str, Any]] = []
    for module in modules:
        if not isinstance(module, dict):
            continue
        for stream in module.get("streams") or []:
            if isinstance(stream, dict) and isinstance(stream.get("write_plan_target"), dict):
                targets.append(stream["write_plan_target"])
    # A concrete stream target can be freshness-checked without decoding and
    # structurally diffing a whole saved form descriptor. Prefer it so this
    # deployment smoke remains bounded on large ConfigCASSave forms.
    return next(
        (
            target
            for target in targets
            if target.get("stream_index") is not None or "#stream:" in str(target.get("module_ref") or "")
        ),
        targets[0] if targets else None,
    )


def first_public_extension_form_candidate(result: dict[str, Any]) -> dict[str, Any] | None:
    candidates: list[dict[str, Any]] = []
    for form in result.get("forms") or []:
        if not isinstance(form, dict):
            continue
        for match in form.get("matches") or []:
            if not isinstance(match, dict):
                continue
            selector = match.get("selector") if isinstance(match.get("selector"), dict) else {}
            if not selector.get("extension") or not selector.get("ref") or not selector.get("form"):
                continue
            if not any(selector.get(key) for key in ("command", "element", "attribute")):
                continue
            if any(key in selector for key in ("table", "file_name", "guid", "form_guid", "module_ref")):
                continue
            for prop in match.get("writable_properties") or []:
                if not isinstance(prop, dict) or prop.get("value_type") != "string":
                    continue
                property_name = str(prop.get("presentation") or prop.get("property") or "").strip()
                if not property_name:
                    continue
                normalized_property = str(prop.get("property") or "").strip().casefold()
                candidates.append(
                    {
                        "priority": 0 if normalized_property == "title" else (2 if normalized_property in {"id", "name"} else 1),
                        "target": {
                            "kind": "form",
                            **{
                                key: value
                                for key, value in selector.items()
                                if key in {"extension", "ref", "form", "command", "element", "attribute"}
                                and value not in (None, "")
                            },
                        },
                        "edit": {
                            "property": property_name,
                            "value": f"{str(prop.get('value') or '')} [NAME-FIRST PREFLIGHT]",
                        },
                    }
                )
    if not candidates:
        return None
    candidate = sorted(candidates, key=lambda item: int(item.get("priority") or 0))[0]
    candidate.pop("priority", None)
    return candidate


def first_public_extension_module_candidate(result: dict[str, Any]) -> dict[str, Any] | None:
    public_keys = {
        "extension",
        "ref",
        "form",
        "module",
        "qualified_name",
        "stream_ordinal",
    }
    opaque_keys = {
        "table",
        "file_name",
        "guid",
        "form_guid",
        "module_guid",
        "object_guid",
        "module_ref",
        "extension_guid",
    }
    for module in result.get("modules") or []:
        if not isinstance(module, dict):
            continue
        for stream in module.get("streams") or []:
            if not isinstance(stream, dict):
                continue
            target = stream.get("write_plan_target") if isinstance(stream.get("write_plan_target"), dict) else {}
            preview = str(stream.get("preview") or "").strip()
            control_fragment = next((line.strip() for line in preview.splitlines() if line.strip()), "")
            if not target.get("extension") or not target.get("ref") or not target.get("module"):
                continue
            if not preview or not control_fragment or any(key in target for key in opaque_keys):
                continue
            public_target = {
                "kind": "module",
                **{
                    key: value
                    for key, value in target.items()
                    if key in public_keys and value not in (None, "")
                },
            }
            return {
                "target": public_target,
                "intent": {
                    "operation": "replace_with_control",
                    "control_fragment": control_fragment,
                    "new": preview,
                },
            }
    return None


def different_extension_guid(layer_id: str) -> str | None:
    if not EXTENSION_GUID_LAYER_RE.fullmatch(str(layer_id or "")):
        return None
    guid = str(layer_id).split(":", 1)[1].lower()
    replacement = "0" if guid[-1] != "0" else "1"
    return f"{guid[:-1]}{replacement}"


def run_smoke(
    endpoint_url: str,
    base_id: str,
    timeout: float,
    *,
    transport: str,
    require_name_first_extension_form: bool = False,
    require_name_first_extension_module: bool = False,
) -> dict[str, Any]:
    failures: list[str] = []
    checks: dict[str, Any] = {}
    session_id: str | None = None
    initialize_result: dict[str, Any] | None = None
    if transport == "mcp":
        session_id, initialize_result = mcp_initialize(endpoint_url, timeout)
        contract_version = (((initialize_result.get("result") or {}).get("serverInfo") or {}).get("contract_version") if isinstance(initialize_result.get("result"), dict) else None)
        checks["mcp.initialize"] = {
            "status": "ok" if session_id and contract_version == EXPECTED_CONTRACT_VERSION else "error",
            "session": bool(session_id),
            "contract_version": contract_version,
        }
        require(bool(session_id), "MCP initialize must return Mcp-Session-Id", failures)
        require(contract_version == EXPECTED_CONTRACT_VERSION, "MCP initialize must report expected contract version", failures)

    help_result = rpc_call(endpoint_url, "help.methods", {}, timeout, transport=transport, session_id=session_id)
    names = method_names(help_result)
    checks["method_exposed"] = {
        "status": "ok" if "metadata.write.preflight" in names else "missing",
        "method_count": len(names),
    }
    require("metadata.write.preflight" in names, "metadata.write.preflight must be exposed by help.methods", failures)

    blocked = rpc_call(
        endpoint_url,
        "metadata.write.preflight",
        {
            "base_id": base_id,
            "target": {"kind": "form", "canonical_path": "Справочник.Контрагенты.Наименование"},
            "intent": {"operation": "property_change", "property": "Заголовок", "value": "SMOKE"},
            "timeout_seconds": int(timeout),
        },
        timeout,
        transport=transport,
        session_id=session_id,
    )
    checks["effective_path_preflight"] = {
        "schema": blocked.get("schema"),
        "status": blocked.get("status"),
        "allowed": blocked.get("allowed"),
        "plan_allowed": (blocked.get("plan") or {}).get("allowed") if isinstance(blocked.get("plan"), dict) else None,
    }
    require(blocked.get("schema") == "onec_metadata_write_preflight.v1", "preflight must return expected schema", failures)
    require(classified_preflight_status(blocked.get("status")), "effective path preflight must classify readiness or a safety gate", failures)
    require(blocked.get("allowed") is False, "effective path preflight must not be allowed", failures)

    target = first_saved_module_target(endpoint_url, base_id, timeout, transport=transport, session_id=session_id)
    if target:
        concrete = rpc_call(
            endpoint_url,
            "metadata.write.preflight",
            {
                "base_id": base_id,
                "target": {"kind": "module", **target},
                "intent": {"operation": "replace_with_control", "control_fragment": "codex-smoke-nonmatching-fragment", "new": "codex-smoke"},
                "timeout_seconds": int(timeout),
            },
            timeout,
            transport=transport,
            session_id=session_id,
        )
        freshness = (concrete.get("saved_state") or {}).get("freshness") if isinstance(concrete.get("saved_state"), dict) else {}
        checks["concrete_saved_state_preflight"] = {
            "schema": concrete.get("schema"),
            "status": concrete.get("status"),
            "allowed": concrete.get("allowed"),
            "saved_state_status": (concrete.get("saved_state") or {}).get("status") if isinstance(concrete.get("saved_state"), dict) else None,
            "freshness": freshness.get("status") if isinstance(freshness, dict) else None,
            "writer": (concrete.get("route") or {}).get("writer") if isinstance(concrete.get("route"), dict) else None,
        }
        require(concrete.get("schema") == "onec_metadata_write_preflight.v1", "concrete preflight must return expected schema", failures)
        require(classified_preflight_status(concrete.get("status")), "concrete preflight must classify readiness or a safety gate", failures)
        if concrete.get("status") == "ready":
            require(freshness.get("status") == "live_sql_verified", "ready concrete preflight must be live SQL verified", failures)
    else:
        checks["concrete_saved_state_preflight"] = {"status": "skipped_no_saved_module_target"}

    forms = rpc_call(
        endpoint_url,
        "metadata.saved_state.forms.search",
        {
            "base_id": base_id,
            "limit": 20,
            "scan_limit": 1000,
            "timeout_seconds": int(timeout),
        },
        timeout,
        transport=transport,
        session_id=session_id,
    )
    form_candidate = first_public_extension_form_candidate(forms)
    if not form_candidate:
        checks["name_first_extension_form_preflight"] = {
            "status": "skipped_no_public_extension_form_target",
            "search_status": forms.get("status"),
            "forms": int((forms.get("counts") or {}).get("forms") or 0),
        }
        checks["conflicting_extension_selector_preflight"] = {
            "status": "skipped_no_public_extension_form_target",
        }
        if require_name_first_extension_form:
            failures.append("name-first extension form preflight target is required but was not discovered")
    else:
        form_preflight = rpc_call(
            endpoint_url,
            "metadata.write.preflight",
            {
                "base_id": base_id,
                "target": form_candidate["target"],
                "edits": [form_candidate["edit"]],
                "resolve_origin": False,
                "timeout_seconds": int(timeout),
            },
            timeout,
            transport=transport,
            session_id=session_id,
        )
        plan = form_preflight.get("plan") if isinstance(form_preflight.get("plan"), dict) else {}
        repository = form_preflight.get("repository") if isinstance(form_preflight.get("repository"), dict) else {}
        support = form_preflight.get("support") if isinstance(form_preflight.get("support"), dict) else {}
        repository_layer = str(repository.get("layer_id") or "")
        support_layer = str(support.get("layer_id") or "")
        name_first = not any(
            key in form_candidate["target"]
            for key in ("table", "file_name", "guid", "form_guid", "module_ref", "extension_guid")
        )
        checks["name_first_extension_form_preflight"] = {
            "schema": form_preflight.get("schema"),
            "status": form_preflight.get("status"),
            "allowed": form_preflight.get("allowed"),
            "plan_status": plan.get("status"),
            "plan_allowed": plan.get("allowed"),
            "repository_layer_id": repository_layer or None,
            "support_layer_id": support_layer or None,
            "name_first": name_first,
            "extension": form_candidate["target"].get("extension"),
        }
        require(form_preflight.get("schema") == "onec_metadata_write_preflight.v1", "name-first extension form preflight must return expected schema", failures)
        require(classified_preflight_status(form_preflight.get("status")), "name-first extension form preflight must classify readiness or a safety gate", failures)
        require(plan.get("status") == "planned" and plan.get("allowed") is True, "name-first extension form preflight plan must be allowed and planned", failures)
        require(name_first, "extension form preflight input must remain name-first", failures)
        require(bool(EXTENSION_GUID_LAYER_RE.fullmatch(repository_layer)), "repository gate must use a resolved extension GUID layer", failures)
        require(repository_layer == support_layer, "repository and support gates must use the same extension layer", failures)
        conflicting_guid = different_extension_guid(repository_layer)
        if not conflicting_guid:
            checks["conflicting_extension_selector_preflight"] = {
                "status": "skipped_no_resolved_extension_layer",
            }
        else:
            conflicting = rpc_call(
                endpoint_url,
                "metadata.write.preflight",
                {
                    "base_id": base_id,
                    "extension_guid": conflicting_guid,
                    "target": form_candidate["target"],
                    "edits": [form_candidate["edit"]],
                    "resolve_origin": False,
                    "timeout_seconds": int(timeout),
                },
                timeout,
                transport=transport,
                session_id=session_id,
            )
            layer_resolution = (
                conflicting.get("development_layer_resolution")
                if isinstance(conflicting.get("development_layer_resolution"), dict)
                else {}
            )
            layer_diagnostics = (
                layer_resolution.get("diagnostics")
                if isinstance(layer_resolution.get("diagnostics"), dict)
                else {}
            )
            checks["conflicting_extension_selector_preflight"] = {
                "schema": conflicting.get("schema"),
                "status": conflicting.get("status"),
                "allowed": conflicting.get("allowed"),
                "resolution_status": layer_resolution.get("status"),
                "error": layer_diagnostics.get("error"),
                "layer_id": layer_resolution.get("layer_id"),
            }
            require(conflicting.get("schema") == "onec_metadata_write_preflight.v1", "conflicting extension selector preflight must return expected schema", failures)
            require(conflicting.get("status") == "blocked" and conflicting.get("allowed") is False, "conflicting extension name/GUID must block preflight", failures)
            require(layer_resolution.get("status") == "conflict", "conflicting extension selector must report conflict resolution status", failures)
            require(layer_diagnostics.get("error") == "extension_selector_conflict", "conflicting extension selector must report extension_selector_conflict", failures)

    modules = rpc_call(
        endpoint_url,
        "metadata.saved_state.modules.search",
        {
            "base_id": base_id,
            "tables": ["ConfigCASSave"],
            "limit": 20,
            "scan_limit": 1000,
            "timeout_seconds": int(timeout),
        },
        timeout,
        transport=transport,
        session_id=session_id,
    )
    module_candidate = first_public_extension_module_candidate(modules)
    if not module_candidate:
        checks["name_first_extension_module_preflight"] = {
            "status": "skipped_no_public_extension_module_target",
            "search_status": modules.get("status"),
            "modules": int((modules.get("counts") or {}).get("modules") or 0),
        }
        checks["conflicting_extension_module_selector_preflight"] = {
            "status": "skipped_no_public_extension_module_target",
        }
        if require_name_first_extension_module:
            failures.append("name-first extension module preflight target is required but was not discovered")
    else:
        module_preflight = rpc_call(
            endpoint_url,
            "metadata.write.preflight",
            {
                "base_id": base_id,
                "target": module_candidate["target"],
                "intent": module_candidate["intent"],
                "resolve_origin": False,
                "timeout_seconds": int(timeout),
            },
            timeout,
            transport=transport,
            session_id=session_id,
        )
        plan = module_preflight.get("plan") if isinstance(module_preflight.get("plan"), dict) else {}
        repository = module_preflight.get("repository") if isinstance(module_preflight.get("repository"), dict) else {}
        support = module_preflight.get("support") if isinstance(module_preflight.get("support"), dict) else {}
        repository_layer = str(repository.get("layer_id") or "")
        support_layer = str(support.get("layer_id") or "")
        name_first = not any(
            key in module_candidate["target"]
            for key in ("table", "file_name", "guid", "form_guid", "module_guid", "object_guid", "module_ref", "extension_guid")
        )
        checks["name_first_extension_module_preflight"] = {
            "schema": module_preflight.get("schema"),
            "status": module_preflight.get("status"),
            "allowed": module_preflight.get("allowed"),
            "plan_status": plan.get("status"),
            "plan_allowed": plan.get("allowed"),
            "repository_layer_id": repository_layer or None,
            "support_layer_id": support_layer or None,
            "name_first": name_first,
            "extension": module_candidate["target"].get("extension"),
        }
        require(module_preflight.get("schema") == "onec_metadata_write_preflight.v1", "name-first extension module preflight must return expected schema", failures)
        require(classified_preflight_status(module_preflight.get("status")), "name-first extension module preflight must classify readiness or a safety gate", failures)
        require(plan.get("status") == "planned" and plan.get("allowed") is True, "name-first extension module preflight plan must be allowed and planned", failures)
        require(name_first, "extension module preflight input must remain name-first", failures)
        require(bool(EXTENSION_GUID_LAYER_RE.fullmatch(repository_layer)), "module repository gate must use a resolved extension GUID layer", failures)
        require(repository_layer == support_layer, "module repository and support gates must use the same extension layer", failures)
        conflicting_guid = different_extension_guid(repository_layer)
        if not conflicting_guid:
            checks["conflicting_extension_module_selector_preflight"] = {
                "status": "skipped_no_resolved_extension_layer",
            }
        else:
            conflicting = rpc_call(
                endpoint_url,
                "metadata.write.preflight",
                {
                    "base_id": base_id,
                    "extension_guid": conflicting_guid,
                    "target": module_candidate["target"],
                    "intent": module_candidate["intent"],
                    "resolve_origin": False,
                    "timeout_seconds": int(timeout),
                },
                timeout,
                transport=transport,
                session_id=session_id,
            )
            layer_resolution = (
                conflicting.get("development_layer_resolution")
                if isinstance(conflicting.get("development_layer_resolution"), dict)
                else {}
            )
            layer_diagnostics = (
                layer_resolution.get("diagnostics")
                if isinstance(layer_resolution.get("diagnostics"), dict)
                else {}
            )
            checks["conflicting_extension_module_selector_preflight"] = {
                "schema": conflicting.get("schema"),
                "status": conflicting.get("status"),
                "allowed": conflicting.get("allowed"),
                "resolution_status": layer_resolution.get("status"),
                "error": layer_diagnostics.get("error"),
                "layer_id": layer_resolution.get("layer_id"),
            }
            require(conflicting.get("schema") == "onec_metadata_write_preflight.v1", "conflicting extension module selector preflight must return expected schema", failures)
            require(conflicting.get("status") == "blocked" and conflicting.get("allowed") is False, "conflicting extension module name/GUID must block preflight", failures)
            require(layer_resolution.get("status") == "conflict", "conflicting extension module selector must report conflict resolution status", failures)
            require(layer_diagnostics.get("error") == "extension_selector_conflict", "conflicting extension module selector must report extension_selector_conflict", failures)

    return {
        "schema": "onec_write_preflight_smoke.v1",
        "status": "ok" if not failures else "failed",
        "endpoint_url": endpoint_url,
        "transport": transport,
        "base_id": base_id,
        "requirements": {
            "name_first_extension_form": require_name_first_extension_form,
            "name_first_extension_module": require_name_first_extension_module,
        },
        "checks": checks,
        "failures": failures,
        **({"mcp_initialize": initialize_result} if initialize_result and failures else {}),
    }


def main() -> int:
    parser = argparse.ArgumentParser(description="Smoke deployed metadata.write.preflight behavior without SQL writes.")
    parser.add_argument("--base-url", default=DEFAULT_BASE_URL)
    parser.add_argument("--mcp-url", default=DEFAULT_MCP_URL)
    parser.add_argument("--transport", choices=("rest", "mcp"), default="rest")
    parser.add_argument("--base-id", default="upo_test")
    parser.add_argument("--timeout", type=float, default=30.0)
    parser.add_argument("--report", type=Path)
    parser.add_argument("--require-name-first-extension-form", action="store_true")
    parser.add_argument("--require-name-first-extension-module", action="store_true")
    args = parser.parse_args()

    endpoint_url = args.mcp_url if args.transport == "mcp" else args.base_url
    try:
        report = run_smoke(
            endpoint_url,
            args.base_id,
            args.timeout,
            transport=args.transport,
            require_name_first_extension_form=args.require_name_first_extension_form,
            require_name_first_extension_module=args.require_name_first_extension_module,
        )
    except (TimeoutError, URLError, OSError) as exc:
        report = {
            "schema": "onec_write_preflight_smoke.v1",
            "status": "failed",
            "endpoint_url": endpoint_url,
            "transport": args.transport,
            "base_id": args.base_id,
            "checks": {},
            "failures": [f"transport timeout/error: {type(exc).__name__}: {str(exc)[:300]}"],
        }
    if args.report:
        args.report.parent.mkdir(parents=True, exist_ok=True)
        args.report.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
    print(json.dumps(report, ensure_ascii=False, indent=2))
    return 0 if report["status"] == "ok" else 1


if __name__ == "__main__":
    raise SystemExit(main())
