package api

import (
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/url"
	"time"

	"github.com/mitchellh/mapstructure"
)

const (
	// ACLClientType is the client type token
	ACLClientType = "client"

	// ACLManagementType is the management type token
	ACLManagementType = "management"
)

type ACLLink struct {
	ID   string
	Name string
}

type ACLTokenPolicyLink = ACLLink
type ACLTokenRoleLink = ACLLink

// ACLToken represents an ACL Token
type ACLToken struct {
	CreateIndex       uint64
	ModifyIndex       uint64
	AccessorID        string
	SecretID          string
	Description       string
	Policies          []*ACLTokenPolicyLink `json:",omitempty"`
	Roles             []*ACLTokenRoleLink   `json:",omitempty"`
	ServiceIdentities []*ACLServiceIdentity `json:",omitempty"`
	NodeIdentities    []*ACLNodeIdentity    `json:",omitempty"`
	Local             bool
	AuthMethod        string        `json:",omitempty"`
	ExpirationTTL     time.Duration `json:",omitempty"`
	ExpirationTime    *time.Time    `json:",omitempty"`
	CreateTime        time.Time     `json:",omitempty"`
	Hash              []byte        `json:",omitempty"`

	// DEPRECATED (ACL-Legacy-Compat)
	// Rules will only be present for legacy tokens returned via the new APIs
	Rules string `json:",omitempty"`

	// Namespace is the namespace the ACLToken is associated with.
	// Namespaces are a Consul Enterprise feature.
	Namespace string `json:",omitempty"`
}

type ACLTokenListEntry struct {
	CreateIndex       uint64
	ModifyIndex       uint64
	AccessorID        string
	Description       string
	Policies          []*ACLTokenPolicyLink `json:",omitempty"`
	Roles             []*ACLTokenRoleLink   `json:",omitempty"`
	ServiceIdentities []*ACLServiceIdentity `json:",omitempty"`
	NodeIdentities    []*ACLNodeIdentity    `json:",omitempty"`
	Local             bool
	AuthMethod        string     `json:",omitempty"`
	ExpirationTime    *time.Time `json:",omitempty"`
	CreateTime        time.Time
	Hash              []byte
	Legacy            bool

	// Namespace is the namespace the ACLTokenListEntry is associated with.
	// Namespacing is a Consul Enterprise feature.
	Namespace string `json:",omitempty"`
}

// ACLEntry is used to represent a legacy ACL token
// The legacy tokens are deprecated.
type ACLEntry struct {
	CreateIndex uint64
	ModifyIndex uint64
	ID          string
	Name        string
	Type        string
	Rules       string
}

// ACLReplicationStatus is used to represent the status of ACL replication.
type ACLReplicationStatus struct {
	Enabled              bool
	Running              bool
	SourceDatacenter     string
	ReplicationType      string
	ReplicatedIndex      uint64
	ReplicatedRoleIndex  uint64
	ReplicatedTokenIndex uint64
	LastSuccess          time.Time
	LastError            time.Time
}

// ACLServiceIdentity represents a high-level grant of all necessary privileges
// to assume the identity of the named Service in the Catalog and within
// Connect.
type ACLServiceIdentity struct {
	ServiceName string
	Datacenters []string `json:",omitempty"`
}

// ACLNodeIdentity represents a high-level grant of all necessary privileges
// to assume the identity of the named Node in the Catalog and within Connect.
type ACLNodeIdentity struct {
	NodeName   string
	Datacenter string
}

// ACLPolicy represents an ACL Policy.
type ACLPolicy struct {
	ID          string
	Name        string
	Description string
	Rules       string
	Datacenters []string
	Hash        []byte
	CreateIndex uint64
	ModifyIndex uint64

	// Namespace is the namespace the ACLPolicy is associated with.
	// Namespacing is a Consul Enterprise feature.
	Namespace string `json:",omitempty"`
}

type ACLPolicyListEntry struct {
	ID          string
	Name        string
	Description string
	Datacenters []string
	Hash        []byte
	CreateIndex uint64
	ModifyIndex uint64

	// Namespace is the namespace the ACLPolicyListEntry is associated with.
	// Namespacing is a Consul Enterprise feature.
	Namespace string `json:",omitempty"`
}

type ACLRolePolicyLink = ACLLink

// ACLRole represents an ACL Role.
type ACLRole struct {
	ID                string
	Name              string
	Description       string
	Policies          []*ACLRolePolicyLink  `json:",omitempty"`
	ServiceIdentities []*ACLServiceIdentity `json:",omitempty"`
	NodeIdentities    []*ACLNodeIdentity    `json:",omitempty"`
	Hash              []byte
	CreateIndex       uint64
	ModifyIndex       uint64

	// Namespace is the namespace the ACLRole is associated with.
	// Namespacing is a Consul Enterprise feature.
	Namespace string `json:",omitempty"`
}

// BindingRuleBindType is the type of binding rule mechanism used.
type BindingRuleBindType string

const (
	// BindingRuleBindTypeService binds to a service identity with the given name.
	BindingRuleBindTypeService BindingRuleBindType = "service"

	// BindingRuleBindTypeRole binds to pre-existing roles with the given name.
	BindingRuleBindTypeRole BindingRuleBindType = "role"
)

type ACLBindingRule struct {
	ID          string
	Description string
	AuthMethod  string
	Selector    string
	BindType    BindingRuleBindType
	BindName    string

	CreateIndex uint64
	ModifyIndex uint64

	// Namespace is the namespace the ACLBindingRule is associated with.
	// Namespacing is a Consul Enterprise feature.
	Namespace string `json:",omitempty"`
}

type ACLAuthMethod struct {
	Name        string
	Type        string
	DisplayName string        `json:",omitempty"`
	Description string        `json:",omitempty"`
	MaxTokenTTL time.Duration `json:",omitempty"`

	// TokenLocality defines the kind of token that this auth method produces.
	// This can be either 'local' or 'global'. If empty 'local' is assumed.
	TokenLocality string `json:",omitempty"`

	// Configuration is arbitrary configuration for the auth method. This
	// should only contain primitive values and containers (such as lists and
	// maps).
	Config map[string]interface{}

	CreateIndex uint64
	ModifyIndex uint64

	// NamespaceRules apply only on auth methods defined in the default namespace.
	// Namespacing is a Consul Enterprise feature.
	NamespaceRules []*ACLAuthMethodNamespaceRule `json:",omitempty"`

	// Namespace is the namespace the ACLAuthMethod is associated with.
	// Namespacing is a Consul Enterprise feature.
	Namespace string `json:",omitempty"`
}

func (m *ACLAuthMethod) MarshalJSON() ([]byte, error) {
	type Alias ACLAuthMethod
	exported := &struct {
		MaxTokenTTL string `json:",omitempty"`
		*Alias
	}{
		MaxTokenTTL: m.MaxTokenTTL.String(),
		Alias:       (*Alias)(m),
	}
	if m.MaxTokenTTL == 0 {
		exported.MaxTokenTTL = ""
	}

	return json.Marshal(exported)
}

func (m *ACLAuthMethod) UnmarshalJSON(data []byte) error {
	type Alias ACLAuthMethod
	aux := &struct {
		MaxTokenTTL string
		*Alias
	}{
		Alias: (*Alias)(m),
	}
	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}
	var err error
	if aux.MaxTokenTTL != "" {
		if m.MaxTokenTTL, err = time.ParseDuration(aux.MaxTokenTTL); err != nil {
			return err
		}
	}

	return nil
}

type ACLAuthMethodNamespaceRule struct {
	// Selector is an expression that matches against verified identity
	// attributes returned from the auth method during login.
	Selector string `json:",omitempty"`

	// BindNamespace is the target namespace of the binding. Can be lightly
	// templated using HIL ${foo} syntax from available field names.
	//
	// If empty it's created in the same namespace as the auth method.
	BindNamespace string `json:",omitempty"`
}

type ACLAuthMethodListEntry struct {
	Name        string
	Type        string
	DisplayName string `json:",omitempty"`
	Description string `json:",omitempty"`
	CreateIndex uint64
	ModifyIndex uint64

	// Namespace is the namespace the ACLAuthMethodListEntry is associated with.
	// Namespacing is a Consul Enterprise feature.
	Namespace string `json:",omitempty"`
}

// ParseKubernetesAuthMethodConfig takes a raw config map and returns a parsed
// KubernetesAuthMethodConfig.
func ParseKubernetesAuthMethodConfig(raw map[string]interface{}) (*KubernetesAuthMethodConfig, error) {
	var config KubernetesAuthMethodConfig
	decodeConf := &mapstructure.DecoderConfig{
		Result:           &config,
		WeaklyTypedInput: true,
	}

	decoder, err := mapstructure.NewDecoder(decodeConf)
	if err != nil {
		return nil, err
	}

	if err := decoder.Decode(raw); err != nil {
		return nil, fmt.Errorf("error decoding config: %s", err)
	}

	return &config, nil
}

// KubernetesAuthMethodConfig is the config for the built-in Consul auth method
// for Kubernetes.
type KubernetesAuthMethodConfig struct {
	Host              string `json:",omitempty"`
	CACert            string `json:",omitempty"`
	ServiceAccountJWT string `json:",omitempty"`
}

// RenderToConfig converts this into a map[string]interface{} suitable for use
// in the ACLAuthMethod.Config field.
func (c *KubernetesAuthMethodConfig) RenderToConfig() map[string]interface{} {
	return map[string]interface{}{
		"Host":              c.Host,
		"CACert":            c.CACert,
		"ServiceAccountJWT": c.ServiceAccountJWT,
	}
}

// OIDCAuthMethodConfig is the config for the built-in Consul auth method for
// OIDC and JWT.
type OIDCAuthMethodConfig struct {
	// common for type=oidc and type=jwt
	JWTSupportedAlgs    []string          `json:",omitempty"`
	BoundAudiences      []string          `json:",omitempty"`
	ClaimMappings       map[string]string `json:",omitempty"`
	ListClaimMappings   map[string]string `json:",omitempty"`
	OIDCDiscoveryURL    string            `json:",omitempty"`
	OIDCDiscoveryCACert string            `json:",omitempty"`
	// just for type=oidc
	OIDCClientID        string   `json:",omitempty"`
	OIDCClientSecret    string   `json:",omitempty"`
	OIDCScopes          []string `json:",omitempty"`
	AllowedRedirectURIs []string `json:",omitempty"`
	VerboseOIDCLogging  bool     `json:",omitempty"`
	// just for type=jwt
	JWKSURL              string        `json:",omitempty"`
	JWKSCACert           string        `json:",omitempty"`
	JWTValidationPubKeys []string      `json:",omitempty"`
	BoundIssuer          string        `json:",omitempty"`
	ExpirationLeeway     time.Duration `json:",omitempty"`
	NotBeforeLeeway      time.Duration `json:",omitempty"`
	ClockSkewLeeway      time.Duration `json:",omitempty"`
}

// RenderToConfig converts this into a map[string]interface{} suitable for use
// in the ACLAuthMethod.Config field.
func (c *OIDCAuthMethodConfig) RenderToConfig() map[string]interface{} {
	return map[string]interface{}{
		// common for type=oidc and type=jwt
		"JWTSupportedAlgs":    c.JWTSupportedAlgs,
		"BoundAudiences":      c.BoundAudiences,
		"ClaimMappings":       c.ClaimMappings,
		"ListClaimMappings":   c.ListClaimMappings,
		"OIDCDiscoveryURL":    c.OIDCDiscoveryURL,
		"OIDCDiscoveryCACert": c.OIDCDiscoveryCACert,
		// just for type=oidc
		"OIDCClientID":        c.OIDCClientID,
		"OIDCClientSecret":    c.OIDCClientSecret,
		"OIDCScopes":          c.OIDCScopes,
		"AllowedRedirectURIs": c.AllowedRedirectURIs,
		"VerboseOIDCLogging":  c.VerboseOIDCLogging,
		// just for type=jwt
		"JWKSURL":              c.JWKSURL,
		"JWKSCACert":           c.JWKSCACert,
		"JWTValidationPubKeys": c.JWTValidationPubKeys,
		"BoundIssuer":          c.BoundIssuer,
		"ExpirationLeeway":     c.ExpirationLeeway,
		"NotBeforeLeeway":      c.NotBeforeLeeway,
		"ClockSkewLeeway":      c.ClockSkewLeeway,
	}
}

type ACLLoginParams struct {
	AuthMethod  string
	BearerToken string
	Meta        map[string]string `json:",omitempty"`
}

type ACLOIDCAuthURLParams struct {
	AuthMethod  string
	RedirectURI string
	ClientNonce string
	Meta        map[string]string `json:",omitempty"`
}

// ACL can be used to query the ACL endpoints
type ACL struct {
	c *Client
}

// ACL returns a handle to the ACL endpoints
func (c *Client) ACL() *ACL {
	return &ACL{c}
}

// Bootstrap is used to perform a one-time ACL bootstrap operation on a cluster
// to get the first management token.
func (a *ACL) Bootstrap() (*ACLToken, *WriteMeta, error) {
	r := a.c.newRequest("PUT", "/v1/acl/bootstrap")
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	var out ACLToken
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}
	return &out, wm, nil
}

// Create is used to generate a new token with the given parameters
//
// Deprecated: Use TokenCreate instead.
func (a *ACL) Create(acl *ACLEntry, q *WriteOptions) (string, *WriteMeta, error) {
	r := a.c.newRequest("PUT", "/v1/acl/create")
	r.setWriteOptions(q)
	r.obj = acl
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return "", nil, err
	}
	defer resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	var out struct{ ID string }
	if err := decodeBody(resp, &out); err != nil {
		return "", nil, err
	}
	return out.ID, wm, nil
}

// Update is used to update the rules of an existing token
//
// Deprecated: Use TokenUpdate instead.
func (a *ACL) Update(acl *ACLEntry, q *WriteOptions) (*WriteMeta, error) {
	r := a.c.newRequest("PUT", "/v1/acl/update")
	r.setWriteOptions(q)
	r.obj = acl
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	return wm, nil
}

// Destroy is used to destroy a given ACL token ID
//
// Deprecated: Use TokenDelete instead.
func (a *ACL) Destroy(id string, q *WriteOptions) (*WriteMeta, error) {
	r := a.c.newRequest("PUT", "/v1/acl/destroy/"+id)
	r.setWriteOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, err
	}
	resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	return wm, nil
}

// Clone is used to return a new token cloned from an existing one
//
// Deprecated: Use TokenClone instead.
func (a *ACL) Clone(id string, q *WriteOptions) (string, *WriteMeta, error) {
	r := a.c.newRequest("PUT", "/v1/acl/clone/"+id)
	r.setWriteOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return "", nil, err
	}
	defer resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	var out struct{ ID string }
	if err := decodeBody(resp, &out); err != nil {
		return "", nil, err
	}
	return out.ID, wm, nil
}

// Info is used to query for information about an ACL token
//
// Deprecated: Use TokenRead instead.
func (a *ACL) Info(id string, q *QueryOptions) (*ACLEntry, *QueryMeta, error) {
	r := a.c.newRequest("GET", "/v1/acl/info/"+id)
	r.setQueryOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	qm := &QueryMeta{}
	parseQueryMeta(resp, qm)
	qm.RequestTime = rtt

	var entries []*ACLEntry
	if err := decodeBody(resp, &entries); err != nil {
		return nil, nil, err
	}
	if len(entries) > 0 {
		return entries[0], qm, nil
	}
	return nil, qm, nil
}

// List is used to get all the ACL tokens
//
// Deprecated: Use TokenList instead.
func (a *ACL) List(q *QueryOptions) ([]*ACLEntry, *QueryMeta, error) {
	r := a.c.newRequest("GET", "/v1/acl/list")
	r.setQueryOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	qm := &QueryMeta{}
	parseQueryMeta(resp, qm)
	qm.RequestTime = rtt

	var entries []*ACLEntry
	if err := decodeBody(resp, &entries); err != nil {
		return nil, nil, err
	}
	return entries, qm, nil
}

// Replication returns the status of the ACL replication process in the datacenter
func (a *ACL) Replication(q *QueryOptions) (*ACLReplicationStatus, *QueryMeta, error) {
	r := a.c.newRequest("GET", "/v1/acl/replication")
	r.setQueryOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	qm := &QueryMeta{}
	parseQueryMeta(resp, qm)
	qm.RequestTime = rtt

	var entries *ACLReplicationStatus
	if err := decodeBody(resp, &entries); err != nil {
		return nil, nil, err
	}
	return entries, qm, nil
}

// TokenCreate creates a new ACL token. If either the AccessorID or SecretID fields
// of the ACLToken structure are empty they will be filled in by Consul.
func (a *ACL) TokenCreate(token *ACLToken, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
	r := a.c.newRequest("PUT", "/v1/acl/token")
	r.setWriteOptions(q)
	r.obj = token
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	var out ACLToken
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, wm, nil
}

// TokenUpdate updates a token in place without modifying its AccessorID or SecretID. A valid
// AccessorID must be set in the ACLToken structure passed to this function but the SecretID may
// be omitted and will be filled in by Consul with its existing value.
func (a *ACL) TokenUpdate(token *ACLToken, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
	if token.AccessorID == "" {
		return nil, nil, fmt.Errorf("Must specify an AccessorID for Token Updating")
	}
	r := a.c.newRequest("PUT", "/v1/acl/token/"+token.AccessorID)
	r.setWriteOptions(q)
	r.obj = token
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	var out ACLToken
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, wm, nil
}

// TokenClone will create a new token with the same policies and locality as the original
// token but will have its own auto-generated AccessorID and SecretID as well having the
// description passed to this function. The tokenID parameter must be a valid Accessor ID
// of an existing token.
func (a *ACL) TokenClone(tokenID string, description string, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
	if tokenID == "" {
		return nil, nil, fmt.Errorf("Must specify a tokenID for Token Cloning")
	}

	r := a.c.newRequest("PUT", "/v1/acl/token/"+tokenID+"/clone")
	r.setWriteOptions(q)
	r.obj = struct{ Description string }{description}
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	var out ACLToken
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, wm, nil
}

// TokenDelete removes a single ACL token. The tokenID parameter must be a valid
// Accessor ID of an existing token.
func (a *ACL) TokenDelete(tokenID string, q *WriteOptions) (*WriteMeta, error) {
	r := a.c.newRequest("DELETE", "/v1/acl/token/"+tokenID)
	r.setWriteOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, err
	}
	resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	return wm, nil
}

// TokenRead retrieves the full token details. The tokenID parameter must be a valid
// Accessor ID of an existing token.
func (a *ACL) TokenRead(tokenID string, q *QueryOptions) (*ACLToken, *QueryMeta, error) {
	r := a.c.newRequest("GET", "/v1/acl/token/"+tokenID)
	r.setQueryOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	qm := &QueryMeta{}
	parseQueryMeta(resp, qm)
	qm.RequestTime = rtt

	var out ACLToken
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, qm, nil
}

// TokenReadSelf retrieves the full token details of the token currently
// assigned to the API Client. In this manner its possible to read a token
// by its Secret ID.
func (a *ACL) TokenReadSelf(q *QueryOptions) (*ACLToken, *QueryMeta, error) {
	r := a.c.newRequest("GET", "/v1/acl/token/self")
	r.setQueryOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	qm := &QueryMeta{}
	parseQueryMeta(resp, qm)
	qm.RequestTime = rtt

	var out ACLToken
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, qm, nil
}

// TokenList lists all tokens. The listing does not contain any SecretIDs as those
// may only be retrieved by a call to TokenRead.
func (a *ACL) TokenList(q *QueryOptions) ([]*ACLTokenListEntry, *QueryMeta, error) {
	r := a.c.newRequest("GET", "/v1/acl/tokens")
	r.setQueryOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	qm := &QueryMeta{}
	parseQueryMeta(resp, qm)
	qm.RequestTime = rtt

	var entries []*ACLTokenListEntry
	if err := decodeBody(resp, &entries); err != nil {
		return nil, nil, err
	}
	return entries, qm, nil
}

// PolicyCreate will create a new policy. It is not allowed for the policy parameters
// ID field to be set as this will be generated by Consul while processing the request.
func (a *ACL) PolicyCreate(policy *ACLPolicy, q *WriteOptions) (*ACLPolicy, *WriteMeta, error) {
	if policy.ID != "" {
		return nil, nil, fmt.Errorf("Cannot specify an ID in Policy Creation")
	}
	r := a.c.newRequest("PUT", "/v1/acl/policy")
	r.setWriteOptions(q)
	r.obj = policy
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	var out ACLPolicy
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, wm, nil
}

// PolicyUpdate updates a policy. The ID field of the policy parameter must be set to an
// existing policy ID
func (a *ACL) PolicyUpdate(policy *ACLPolicy, q *WriteOptions) (*ACLPolicy, *WriteMeta, error) {
	if policy.ID == "" {
		return nil, nil, fmt.Errorf("Must specify an ID in Policy Update")
	}

	r := a.c.newRequest("PUT", "/v1/acl/policy/"+policy.ID)
	r.setWriteOptions(q)
	r.obj = policy
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	var out ACLPolicy
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, wm, nil
}

// PolicyDelete deletes a policy given its ID.
func (a *ACL) PolicyDelete(policyID string, q *WriteOptions) (*WriteMeta, error) {
	r := a.c.newRequest("DELETE", "/v1/acl/policy/"+policyID)
	r.setWriteOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, err
	}
	resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	return wm, nil
}

// PolicyRead retrieves the policy details including the rule set.
func (a *ACL) PolicyRead(policyID string, q *QueryOptions) (*ACLPolicy, *QueryMeta, error) {
	r := a.c.newRequest("GET", "/v1/acl/policy/"+policyID)
	r.setQueryOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	qm := &QueryMeta{}
	parseQueryMeta(resp, qm)
	qm.RequestTime = rtt

	var out ACLPolicy
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, qm, nil
}

// PolicyReadByName retrieves the policy details including the rule set with name.
func (a *ACL) PolicyReadByName(policyName string, q *QueryOptions) (*ACLPolicy, *QueryMeta, error) {
	r := a.c.newRequest("GET", "/v1/acl/policy/name/"+url.QueryEscape(policyName))
	r.setQueryOptions(q)
	found, rtt, resp, err := requireNotFoundOrOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	qm := &QueryMeta{}
	parseQueryMeta(resp, qm)
	qm.RequestTime = rtt

	if !found {
		return nil, qm, nil
	}

	var out ACLPolicy
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, qm, nil
}

// PolicyList retrieves a listing of all policies. The listing does not include the
// rules for any policy as those should be retrieved by subsequent calls to PolicyRead.
func (a *ACL) PolicyList(q *QueryOptions) ([]*ACLPolicyListEntry, *QueryMeta, error) {
	r := a.c.newRequest("GET", "/v1/acl/policies")
	r.setQueryOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	qm := &QueryMeta{}
	parseQueryMeta(resp, qm)
	qm.RequestTime = rtt

	var entries []*ACLPolicyListEntry
	if err := decodeBody(resp, &entries); err != nil {
		return nil, nil, err
	}
	return entries, qm, nil
}

// RulesTranslate translates the legacy rule syntax into the current syntax.
//
// Deprecated: Support for the legacy syntax translation will be removed
// when legacy ACL support is removed.
func (a *ACL) RulesTranslate(rules io.Reader) (string, error) {
	r := a.c.newRequest("POST", "/v1/acl/rules/translate")
	r.body = rules
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	qm := &QueryMeta{}
	parseQueryMeta(resp, qm)
	qm.RequestTime = rtt

	ruleBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("Failed to read translated rule body: %v", err)
	}

	return string(ruleBytes), nil
}

// RulesTranslateToken translates the rules associated with the legacy syntax
// into the current syntax and returns the results.
//
// Deprecated: Support for the legacy syntax translation will be removed
// when legacy ACL support is removed.
func (a *ACL) RulesTranslateToken(tokenID string) (string, error) {
	r := a.c.newRequest("GET", "/v1/acl/rules/translate/"+tokenID)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	qm := &QueryMeta{}
	parseQueryMeta(resp, qm)
	qm.RequestTime = rtt

	ruleBytes, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", fmt.Errorf("Failed to read translated rule body: %v", err)
	}

	return string(ruleBytes), nil
}

// RoleCreate will create a new role. It is not allowed for the role parameters
// ID field to be set as this will be generated by Consul while processing the request.
func (a *ACL) RoleCreate(role *ACLRole, q *WriteOptions) (*ACLRole, *WriteMeta, error) {
	if role.ID != "" {
		return nil, nil, fmt.Errorf("Cannot specify an ID in Role Creation")
	}

	r := a.c.newRequest("PUT", "/v1/acl/role")
	r.setWriteOptions(q)
	r.obj = role
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	var out ACLRole
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, wm, nil
}

// RoleUpdate updates a role. The ID field of the role parameter must be set to an
// existing role ID
func (a *ACL) RoleUpdate(role *ACLRole, q *WriteOptions) (*ACLRole, *WriteMeta, error) {
	if role.ID == "" {
		return nil, nil, fmt.Errorf("Must specify an ID in Role Update")
	}

	r := a.c.newRequest("PUT", "/v1/acl/role/"+role.ID)
	r.setWriteOptions(q)
	r.obj = role
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	var out ACLRole
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, wm, nil
}

// RoleDelete deletes a role given its ID.
func (a *ACL) RoleDelete(roleID string, q *WriteOptions) (*WriteMeta, error) {
	r := a.c.newRequest("DELETE", "/v1/acl/role/"+roleID)
	r.setWriteOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, err
	}
	resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	return wm, nil
}

// RoleRead retrieves the role details (by ID). Returns nil if not found.
func (a *ACL) RoleRead(roleID string, q *QueryOptions) (*ACLRole, *QueryMeta, error) {
	r := a.c.newRequest("GET", "/v1/acl/role/"+roleID)
	r.setQueryOptions(q)
	found, rtt, resp, err := requireNotFoundOrOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	qm := &QueryMeta{}
	parseQueryMeta(resp, qm)
	qm.RequestTime = rtt

	if !found {
		return nil, qm, nil
	}

	var out ACLRole
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, qm, nil
}

// RoleReadByName retrieves the role details (by name). Returns nil if not found.
func (a *ACL) RoleReadByName(roleName string, q *QueryOptions) (*ACLRole, *QueryMeta, error) {
	r := a.c.newRequest("GET", "/v1/acl/role/name/"+url.QueryEscape(roleName))
	r.setQueryOptions(q)
	found, rtt, resp, err := requireNotFoundOrOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	qm := &QueryMeta{}
	parseQueryMeta(resp, qm)
	qm.RequestTime = rtt

	if !found {
		return nil, qm, nil
	}

	var out ACLRole
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, qm, nil
}

// RoleList retrieves a listing of all roles. The listing does not include some
// metadata for the role as those should be retrieved by subsequent calls to
// RoleRead.
func (a *ACL) RoleList(q *QueryOptions) ([]*ACLRole, *QueryMeta, error) {
	r := a.c.newRequest("GET", "/v1/acl/roles")
	r.setQueryOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	qm := &QueryMeta{}
	parseQueryMeta(resp, qm)
	qm.RequestTime = rtt

	var entries []*ACLRole
	if err := decodeBody(resp, &entries); err != nil {
		return nil, nil, err
	}
	return entries, qm, nil
}

// AuthMethodCreate will create a new auth method.
func (a *ACL) AuthMethodCreate(method *ACLAuthMethod, q *WriteOptions) (*ACLAuthMethod, *WriteMeta, error) {
	if method.Name == "" {
		return nil, nil, fmt.Errorf("Must specify a Name in Auth Method Creation")
	}

	r := a.c.newRequest("PUT", "/v1/acl/auth-method")
	r.setWriteOptions(q)
	r.obj = method
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	var out ACLAuthMethod
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, wm, nil
}

// AuthMethodUpdate updates an auth method.
func (a *ACL) AuthMethodUpdate(method *ACLAuthMethod, q *WriteOptions) (*ACLAuthMethod, *WriteMeta, error) {
	if method.Name == "" {
		return nil, nil, fmt.Errorf("Must specify a Name in Auth Method Update")
	}

	r := a.c.newRequest("PUT", "/v1/acl/auth-method/"+url.QueryEscape(method.Name))
	r.setWriteOptions(q)
	r.obj = method
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	var out ACLAuthMethod
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, wm, nil
}

// AuthMethodDelete deletes an auth method given its Name.
func (a *ACL) AuthMethodDelete(methodName string, q *WriteOptions) (*WriteMeta, error) {
	if methodName == "" {
		return nil, fmt.Errorf("Must specify a Name in Auth Method Delete")
	}

	r := a.c.newRequest("DELETE", "/v1/acl/auth-method/"+url.QueryEscape(methodName))
	r.setWriteOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, err
	}
	resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	return wm, nil
}

// AuthMethodRead retrieves the auth method. Returns nil if not found.
func (a *ACL) AuthMethodRead(methodName string, q *QueryOptions) (*ACLAuthMethod, *QueryMeta, error) {
	if methodName == "" {
		return nil, nil, fmt.Errorf("Must specify a Name in Auth Method Read")
	}

	r := a.c.newRequest("GET", "/v1/acl/auth-method/"+url.QueryEscape(methodName))
	r.setQueryOptions(q)
	found, rtt, resp, err := requireNotFoundOrOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	qm := &QueryMeta{}
	parseQueryMeta(resp, qm)
	qm.RequestTime = rtt

	if !found {
		return nil, qm, nil
	}

	var out ACLAuthMethod
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, qm, nil
}

// AuthMethodList retrieves a listing of all auth methods. The listing does not
// include some metadata for the auth method as those should be retrieved by
// subsequent calls to AuthMethodRead.
func (a *ACL) AuthMethodList(q *QueryOptions) ([]*ACLAuthMethodListEntry, *QueryMeta, error) {
	r := a.c.newRequest("GET", "/v1/acl/auth-methods")
	r.setQueryOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	qm := &QueryMeta{}
	parseQueryMeta(resp, qm)
	qm.RequestTime = rtt

	var entries []*ACLAuthMethodListEntry
	if err := decodeBody(resp, &entries); err != nil {
		return nil, nil, err
	}
	return entries, qm, nil
}

// BindingRuleCreate will create a new binding rule. It is not allowed for the
// binding rule parameter's ID field to be set as this will be generated by
// Consul while processing the request.
func (a *ACL) BindingRuleCreate(rule *ACLBindingRule, q *WriteOptions) (*ACLBindingRule, *WriteMeta, error) {
	if rule.ID != "" {
		return nil, nil, fmt.Errorf("Cannot specify an ID in Binding Rule Creation")
	}

	r := a.c.newRequest("PUT", "/v1/acl/binding-rule")
	r.setWriteOptions(q)
	r.obj = rule
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	var out ACLBindingRule
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, wm, nil
}

// BindingRuleUpdate updates a binding rule. The ID field of the role binding
// rule parameter must be set to an existing binding rule ID.
func (a *ACL) BindingRuleUpdate(rule *ACLBindingRule, q *WriteOptions) (*ACLBindingRule, *WriteMeta, error) {
	if rule.ID == "" {
		return nil, nil, fmt.Errorf("Must specify an ID in Binding Rule Update")
	}

	r := a.c.newRequest("PUT", "/v1/acl/binding-rule/"+rule.ID)
	r.setWriteOptions(q)
	r.obj = rule
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	var out ACLBindingRule
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, wm, nil
}

// BindingRuleDelete deletes a binding rule given its ID.
func (a *ACL) BindingRuleDelete(bindingRuleID string, q *WriteOptions) (*WriteMeta, error) {
	r := a.c.newRequest("DELETE", "/v1/acl/binding-rule/"+bindingRuleID)
	r.setWriteOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, err
	}
	resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	return wm, nil
}

// BindingRuleRead retrieves the binding rule details. Returns nil if not found.
func (a *ACL) BindingRuleRead(bindingRuleID string, q *QueryOptions) (*ACLBindingRule, *QueryMeta, error) {
	r := a.c.newRequest("GET", "/v1/acl/binding-rule/"+bindingRuleID)
	r.setQueryOptions(q)
	found, rtt, resp, err := requireNotFoundOrOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	qm := &QueryMeta{}
	parseQueryMeta(resp, qm)
	qm.RequestTime = rtt

	if !found {
		return nil, qm, nil
	}

	var out ACLBindingRule
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}

	return &out, qm, nil
}

// BindingRuleList retrieves a listing of all binding rules.
func (a *ACL) BindingRuleList(methodName string, q *QueryOptions) ([]*ACLBindingRule, *QueryMeta, error) {
	r := a.c.newRequest("GET", "/v1/acl/binding-rules")
	if methodName != "" {
		r.params.Set("authmethod", methodName)
	}
	r.setQueryOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	qm := &QueryMeta{}
	parseQueryMeta(resp, qm)
	qm.RequestTime = rtt

	var entries []*ACLBindingRule
	if err := decodeBody(resp, &entries); err != nil {
		return nil, nil, err
	}
	return entries, qm, nil
}

// Login is used to exchange auth method credentials for a newly-minted Consul Token.
func (a *ACL) Login(auth *ACLLoginParams, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
	r := a.c.newRequest("POST", "/v1/acl/login")
	r.setWriteOptions(q)
	r.obj = auth

	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	var out ACLToken
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}
	return &out, wm, nil
}

// Logout is used to destroy a Consul Token created via Login().
func (a *ACL) Logout(q *WriteOptions) (*WriteMeta, error) {
	r := a.c.newRequest("POST", "/v1/acl/logout")
	r.setWriteOptions(q)
	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, err
	}
	resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	return wm, nil
}

// OIDCAuthURL requests an authorization URL to start an OIDC login flow.
func (a *ACL) OIDCAuthURL(auth *ACLOIDCAuthURLParams, q *WriteOptions) (string, *WriteMeta, error) {
	if auth.AuthMethod == "" {
		return "", nil, fmt.Errorf("Must specify an auth method name")
	}

	r := a.c.newRequest("POST", "/v1/acl/oidc/auth-url")
	r.setWriteOptions(q)
	r.obj = auth

	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return "", nil, err
	}
	defer resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	var out aclOIDCAuthURLResponse
	if err := decodeBody(resp, &out); err != nil {
		return "", nil, err
	}
	return out.AuthURL, wm, nil
}

type aclOIDCAuthURLResponse struct {
	AuthURL string
}

type ACLOIDCCallbackParams struct {
	AuthMethod  string
	State       string
	Code        string
	ClientNonce string
}

// OIDCCallback is the callback endpoint to complete an OIDC login.
func (a *ACL) OIDCCallback(auth *ACLOIDCCallbackParams, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
	if auth.AuthMethod == "" {
		return nil, nil, fmt.Errorf("Must specify an auth method name")
	}

	r := a.c.newRequest("POST", "/v1/acl/oidc/callback")
	r.setWriteOptions(q)
	r.obj = auth

	rtt, resp, err := requireOK(a.c.doRequest(r))
	if err != nil {
		return nil, nil, err
	}
	defer resp.Body.Close()

	wm := &WriteMeta{RequestTime: rtt}
	var out ACLToken
	if err := decodeBody(resp, &out); err != nil {
		return nil, nil, err
	}
	return &out, wm, nil
}
