///|
fn management_command() -> @argparse.Command {
  @argparse.Command(
    "moonhub",
    subcommand_required=true,
    options=[
      OptionArg(
        "db",
        long="db",
        env="DB_PATH",
        default_values=["moonhub.db"],
        global=true,
      ),
      OptionArg(
        "root",
        long="root",
        env="MOONHUB_REPO_ROOT",
        default_values=["repos"],
        global=true,
      ),
    ],
    subcommands=[
      @argparse.Command("ssh-authorized-key", hidden=true, positionals=[
        PositionArg("system-user"),
        PositionArg("type"),
        PositionArg("key"),
      ]),
      @argparse.Command("ssh-serve", hidden=true, positionals=[
        PositionArg("id"),
      ]),
      @argparse.Command("ssh-pre-receive", hidden=true, positionals=[
        PositionArg("id"),
        PositionArg("repository"),
      ]),
      @argparse.Command("ssh-ref-transaction", hidden=true, positionals=[
        PositionArg("id"),
        PositionArg("repository"),
        PositionArg("push-id"),
        PositionArg("phase"),
      ]),
      @argparse.Command(
        "ssh-key-add",
        about="Bind an Ed25519 public key to an active user.",
        positionals=[PositionArg("user")],
        options=[
          OptionArg("title", long="title", default_values=["SSH key"]),
          OptionArg(
            "file",
            long="file",
            about="Public key file; reads stdin when omitted.",
          ),
        ],
      ),
      @argparse.Command("ssh-key-list", about="List registered SSH keys.", options=[
        OptionArg("user", long="user"),
      ]),
      @argparse.Command(
        "ssh-key-revoke",
        about="Revoke an SSH key immediately for new operations.",
        positionals=[PositionArg("id")],
      ),
      @argparse.Command(
        "repo-create",
        about="Create a registered bare repository, or register an existing one.",
        positionals=[PositionArg("repository")],
        flags=[
          FlagArg(
            "import",
            long="import",
            about="Register an existing bare repository without changing its Git data.",
          ),
        ],
        options=[
          OptionArg("visibility", long="visibility", default_values=["private"]),
        ],
      ),
      @argparse.Command(
        "deploy-key-add",
        about="Bind a new Ed25519 public key to exactly one repository.",
        positionals=[PositionArg("repository")],
        options=[
          OptionArg("actor", long="actor", required=true),
          OptionArg("title", long="title", required=true),
          OptionArg(
            "file",
            long="file",
            required=true,
            about="Public key file.",
          ),
        ],
        flags=[
          FlagArg(
            "write",
            long="write",
            about="Explicitly grant push access; defaults to read-only.",
          ),
        ],
      ),
      @argparse.Command(
        "deploy-key-list",
        about="List repository deploy keys.",
        positionals=[PositionArg("repository")],
        options=[OptionArg("actor", long="actor", required=true)],
      ),
      @argparse.Command(
        "deploy-key-revoke",
        about="Revoke a repository deploy key for subsequent operations.",
        positionals=[PositionArg("repository"), PositionArg("id")],
        options=[OptionArg("actor", long="actor", required=true)],
      ),
      @argparse.Command(
        "pipeline-provider-create",
        about="Register a Docker pipeline provider from a token file.",
        options=[
          OptionArg("name", long="name", required=true),
          OptionArg("token-file", long="token-file", required=true),
          OptionArg("labels", long="labels", default_values=[
            "self-hosted,linux",
          ]),
          OptionArg(
            "scope",
            long="scope",
            default_values=["selected"],
            about="Repository access: selected or all.",
          ),
          OptionArg("repositories", long="repositories", default_values=[""]),
          OptionArg("admin", long="admin", default_values=["root"]),
        ],
      ),
      @argparse.Command(
        "pipeline-provider-ensure",
        about="Idempotently register a Docker pipeline provider from a token file.",
        flags=[
          FlagArg(
            "preserve-existing",
            long="preserve-existing",
            about="Only initialize new providers; preserve existing Web settings.",
          ),
        ],
        options=[
          OptionArg("name", long="name", required=true),
          OptionArg("token-file", long="token-file", required=true),
          OptionArg("labels", long="labels", default_values=[
            "self-hosted,linux",
          ]),
          OptionArg(
            "scope",
            long="scope",
            about="Repository access: selected or all. Omit to preserve existing scope.",
          ),
          OptionArg("repositories", long="repositories", default_values=[""]),
          OptionArg("admin", long="admin", default_values=["root"]),
        ],
      ),
      @argparse.Command(
        "pipeline-rerun",
        about="Rerun a completed pipeline from its immutable source snapshot.",
        positionals=[PositionArg("repository"), PositionArg("number")],
        options=[OptionArg("admin", long="admin", default_values=["root"])],
      ),
    ],
  )
}

///|
fn cli_value(matches : @argparse.Matches, name : String) -> String raise {
  guard single_value(matches, name) is Some(value) else {
    raise @domain.BadRequest(message="missing \{name}")
  }
  value
}

///|
fn cli_key_id(matches : @argparse.Matches) -> Int64 raise {
  let value = cli_value(matches, "id")
  let id = @string.parse_int64(value) catch {
    _ => raise @domain.BadRequest(message="invalid SSH key id")
  }
  guard id > 0L else { raise @domain.BadRequest(message="invalid SSH key id") }
  id
}

///|
async fn run_management_cli(args : ArrayView[String]) -> Int32 {
  let matches = management_command().parse(argv=args, env=@env.get_env_vars())
  guard matches.subcommand is Some((command, options)) else { return 1 }
  let db_path = cli_value(matches, "db")
  guard @fs.exists(db_path) else {
    raise @domain.NotFound(
      message="database not found; start the web service first",
    )
  }
  @async.with_task_group(group => {
    let pool = @database.new_db_pool({ path: db_path, }, group)
    defer pool.close()
    let version : Int64 = pool.with_client(client => {
      guard client.query_opt("PRAGMA user_version") is Some(row) else {
        raise @domain.BadRequest(message="cannot read database schema version")
      }
      row.get_name("user_version")
    })
    guard version == @database.database_schema_version else {
      raise @domain.BadRequest(
        message="database requires migration; start the matching web service first",
      )
    }
    match command {
      "ssh-authorized-key" => {
        guard cli_value(options, "system-user") == "git" else { return 0 }
        let key = @ssh.parse_ssh_public_key(
          "\{cli_value(options, "type")} \{cli_value(options, "key")}",
        ) catch {
          _ => return 0
        }
        if @ssh.authorized_ssh_key(pool, key) is Some(line) {
          println(line)
        }
      }
      "ssh-serve" =>
        return @ssh.serve_git_ssh(
          pool,
          @ssh.SshCredential::parse(cli_value(options, "id")),
          cli_value(matches, "root"),
        )
      "ssh-ref-transaction" =>
        @ssh.record_ref_transaction(
          pool,
          @ssh.SshCredential::parse(cli_value(options, "id")),
          @domain.parse_transport_repo(cli_value(options, "repository")),
          cli_value(options, "push-id"),
          cli_value(options, "phase"),
        )
      "ssh-pre-receive" =>
        @ssh.check_received_refs(
          pool,
          @ssh.SshCredential::parse(cli_value(options, "id")),
          @domain.parse_transport_repo(cli_value(options, "repository")),
        )
      "ssh-key-add" => {
        let user = @identity.active_user_by_name(
          pool,
          cli_value(options, "user"),
        )
        let text = match single_value(options, "file") {
          Some(path) => @fs.read_file(path).text()
          None => @stdio.stdin.read_all().text()
        }
        let key = @ssh.parse_ssh_public_key(text)
        let id = @ssh.add_ssh_key(pool, user, cli_value(options, "title"), key)
        println("\{id}\t\{user.name_key}\t\{key.fingerprint}")
      }
      "ssh-key-list" => {
        let user = single_value(options, "user").unwrap_or("")
        pool.with_client(client => {
          let rows = client.query_all(
            (
              #|SELECT ssh_key.id, "user".name_key, ssh_key.title, ssh_key.fingerprint,
              #|  ssh_key.last_used_at, ssh_key.revoked_at
              #|FROM ssh_key JOIN "user" ON "user".id = ssh_key.user_id
              #|WHERE ? = '' OR "user".name_key = ? ORDER BY ssh_key.id
            ),
            params=[user, user],
          )
          println("ID\tUSER\tTITLE\tFINGERPRINT\tLAST_USED\tREVOKED")
          for row in rows {
            let id : Int64 = row.get_name("id")
            let name : String = row.get_name("name_key")
            let title : String = row.get_name("title")
            let fingerprint : String = row.get_name("fingerprint")
            let last_used : String? = row.get_name("last_used_at")
            let revoked : String? = row.get_name("revoked_at")
            println(
              "\{id}\t\{name}\t\{title}\t\{fingerprint}\t\{last_used.unwrap_or("-")}\t\{revoked.unwrap_or("-")}",
            )
          }
        })
      }
      "ssh-key-revoke" => {
        let id = cli_key_id(options)
        let changed = pool.with_client(client => {
          client.execute(
            "UPDATE ssh_key SET revoked_at = datetime('now') WHERE id = ? AND revoked_at IS NULL",
            params=[id],
          )
        })
        guard changed == 1 else {
          raise @domain.NotFound(message="active SSH key not found")
        }
        println("Revoked SSH key \{id}")
      }
      "repo-create" => {
        let repo = @domain.parse_transport_repo(
          cli_value(options, "repository"),
        )
        @repository.create_managed_repository(
          pool,
          cli_value(matches, "root"),
          repo,
          cli_value(options, "visibility"),
          options.flags.get("import").unwrap_or(false),
        )
        println("Registered \{repo.path()}")
      }
      "deploy-key-add" | "deploy-key-list" | "deploy-key-revoke" => {
        let repo = @domain.parse_transport_repo(
          cli_value(options, "repository"),
        )
        let actor = @identity.active_user_by_name(
          pool,
          cli_value(options, "actor"),
        )
        match command {
          "deploy-key-add" => {
            let path = cli_value(options, "file")
            guard @fs.kind(path, follow_symlink=false) is Regular &&
              @fs.file_size(path) <= 16384L else {
              raise @domain.BadRequest(
                message="public key file must be a regular small file",
              )
            }
            let key = @ssh.parse_ssh_public_key(@fs.read_file(path).text())
            let id = @ssh.add_repository_deploy_key(
              pool,
              actor,
              repo,
              cli_value(options, "title"),
              key,
              write_access=options.flags.get("write").unwrap_or(false),
            )
            println("\{id}\t\{repo.path()}\t\{key.fingerprint}")
          }
          "deploy-key-revoke" => {
            let id = cli_key_id(options)
            @ssh.revoke_repository_deploy_key(pool, actor, repo, id)
            println("Revoked deploy key \{id} for \{repo.path()}")
          }
          _ =>
            for key in @ssh.repository_deploy_keys(pool, actor, repo) {
              println(
                "\{key.id}\t\{key.title}\t\{key.fingerprint}\twrite=\{key.write_access}\tcreator=\{key.creator}\t\{key.created_at}\t\{key.last_used_at.unwrap_or("-")}\t\{key.revoked_at.unwrap_or("-")}",
              )
            }
        }
      }
      "pipeline-provider-create" => {
        let token_path = cli_value(options, "token-file")
        guard @fs.kind(token_path, follow_symlink=false) is Regular &&
          @fs.file_size(token_path) <= 256L else {
          raise @domain.BadRequest(
            message="provider token file must be a regular small file",
          )
        }
        let token = @fs.read_file(token_path).text().trim().to_owned()
        let labels = cli_value(options, "labels")
          .split(",")
          .filter(label => !label.trim().is_empty())
          .map(label => label.trim().to_owned())
          .collect()
        let repositories : Array[@domain.RepoId] = []
        for path in cli_value(options, "repositories").split(",") {
          let path = path.trim()
          if !path.is_empty() {
            repositories.push(@domain.parse_transport_repo(path.to_owned()))
          }
        }
        let admin = @identity.active_user_by_name(
          pool,
          cli_value(options, "admin"),
        )
        let provider = @pipeline.Coordinator::new(pool).create_provider(
          admin,
          cli_value(options, "name"),
          token,
          labels,
          repositories,
          scope=@pipeline.ProviderScope::parse(cli_value(options, "scope")),
        )
        println("Registered pipeline provider \{provider.id}:\{provider.name}")
      }
      "pipeline-provider-ensure" => {
        let token_path = cli_value(options, "token-file")
        guard @fs.kind(token_path, follow_symlink=false) is Regular &&
          @fs.file_size(token_path) <= 256L else {
          raise @domain.BadRequest(
            message="provider token file must be a regular small file",
          )
        }
        let token = @fs.read_file(token_path).text().trim().to_owned()
        let labels = cli_value(options, "labels")
          .split(",")
          .filter(label => !label.trim().is_empty())
          .map(label => label.trim().to_owned())
          .collect()
        let repositories : Array[@domain.RepoId] = []
        for path in cli_value(options, "repositories").split(",") {
          let path = path.trim()
          if !path.is_empty() {
            repositories.push(@domain.parse_transport_repo(path.to_owned()))
          }
        }
        let admin = @identity.active_user_by_name(
          pool,
          cli_value(options, "admin"),
        )
        let provider = @pipeline.Coordinator::new(pool).ensure_provider(
          admin,
          cli_value(options, "name"),
          token,
          labels,
          repositories,
          preserve_existing=options.flags
            .get("preserve-existing")
            .unwrap_or(false),
          scope=match single_value(options, "scope") {
            Some(value) => Some(@pipeline.ProviderScope::parse(value))
            None => None
          },
        )
        println("Ensured pipeline provider \{provider.id}:\{provider.name}")
      }
      "pipeline-rerun" => {
        let number = @string.parse_int64(cli_value(matches, "number")) catch {
          _ => raise @domain.BadRequest(message="invalid pipeline run number")
        }
        guard number > 0L else {
          raise @domain.BadRequest(message="invalid pipeline run number")
        }
        let repo = @domain.parse_transport_repo(
          cli_value(matches, "repository"),
        )
        let actor = @identity.active_user_by_name(
          pool,
          cli_value(options, "admin"),
        )
        let run = @pipeline.rerun_run(
          pool,
          cli_value(matches, "root"),
          actor,
          repo,
          number,
        )
        println("Rerun pipeline \{repo.path()}#\{run.number}")
      }
      _ => raise @domain.BadRequest(message="unknown management command")
    }
    0
  })
}