"""Upload one file and submit a replace-only Catalog Ingestion Run.

This mutates the selected environment. Authenticate as an organization or
platform administrator and confirm the Catalog UUID before running it. Use this
single-file example only when target_table is the Catalog's complete declared
destination set. It prints a submission receipt, not a publication proof; poll
get_catalog_ingestion(catalog_id, job_id) until succeeded/failed/canceled.
"""

from __future__ import annotations

import argparse
import json
import re
import uuid
from collections.abc import Sequence
from pathlib import Path
from typing import Any, Protocol


class CatalogIngestionClient(Protocol):
    """Small client surface used by this executable quickstart."""

    def upload_catalog_import_file(
        self,
        catalog_id: str,
        file_path: str | Path,
        *,
        idempotency_key: str,
        wait_until_claimed: bool,
    ) -> dict[str, Any]: ...

    def submit_catalog_ingestion(
        self,
        catalog_id: str,
        *,
        sources: list[dict[str, str]],
        idempotency_key: str,
        version_label: str | None = None,
    ) -> dict[str, Any]: ...


def upload_and_submit(
    client: CatalogIngestionClient,
    *,
    catalog_id: str,
    file_path: Path,
    target_table: str,
    operation_id: str,
    version_label: str | None,
) -> dict[str, Any]:
    """Complete governed upload verification before submitting ingestion."""

    try:
        if str(uuid.UUID(catalog_id)) != catalog_id:
            raise ValueError("catalog_id must be a canonical Catalog UUID")
    except (AttributeError, TypeError, ValueError) as exc:
        raise ValueError("catalog_id must be a canonical Catalog UUID") from exc
    if (
        not isinstance(target_table, str)
        or re.fullmatch(r"[a-z][a-z0-9_]*", target_table) is None
    ):
        raise ValueError("target_table must match [a-z][a-z0-9_]*")
    if not file_path.is_file():
        raise FileNotFoundError(file_path)
    if (
        not isinstance(operation_id, str)
        or operation_id != operation_id.strip()
        or not 1 <= len(operation_id) <= 246
    ):
        raise ValueError("operation_id must contain 1–246 exact characters")
    if version_label is not None and (
        not isinstance(version_label, str)
        or version_label != version_label.strip()
        or not 1 <= len(version_label) <= 50
    ):
        raise ValueError("version_label must contain 1–50 exact characters")

    upload = client.upload_catalog_import_file(
        catalog_id,
        file_path,
        idempotency_key=f"{operation_id}:upload",
        wait_until_claimed=True,
    )
    if upload.get("status") != "claimed":
        raise RuntimeError("Upload is not claimed; ingestion was not submitted")
    upload_id = upload.get("uploadId")
    if not isinstance(upload_id, str) or not upload_id:
        raise RuntimeError("Claimed upload response did not contain uploadId")

    return client.submit_catalog_ingestion(
        catalog_id,
        sources=[{"uploadId": upload_id, "targetTable": target_table}],
        idempotency_key=f"{operation_id}:ingestion",
        version_label=version_label,
    )


def main(
    argv: Sequence[str] | None = None,
    *,
    client: CatalogIngestionClient | None = None,
) -> int:
    parser = argparse.ArgumentParser(
        description="Upload one file and submit replace-only Catalog Ingestion."
    )
    parser.add_argument("catalog_id", help="Discovered Catalog UUID.")
    parser.add_argument("file", type=Path)
    parser.add_argument("target_table")
    parser.add_argument(
        "--operation-id",
        required=True,
        help="Stable local operation identifier reused after ambiguous failures.",
    )
    parser.add_argument("--version-label")
    parser.add_argument(
        "--env",
        choices=("dev", "staging", "prod"),
        default="dev",
        help="Authenticated HLA-Compass environment (default: dev).",
    )
    args = parser.parse_args(argv)

    if client is None:
        from hla_compass.client import APIClient

        client = APIClient(environment=args.env)

    job = upload_and_submit(
        client,
        catalog_id=args.catalog_id,
        file_path=args.file,
        target_table=args.target_table,
        operation_id=args.operation_id,
        version_label=args.version_label,
    )
    print(json.dumps(job, indent=2, sort_keys=True))
    return 0


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