// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package api

import (
	"encoding/json"
	"errors"
	"fmt"
	"time"
)

// ACLPolicies is used to query the ACL Policy endpoints.
type ACLPolicies struct {
	client *Client
}

// ACLPolicies returns a new handle on the ACL policies.
func (c *Client) ACLPolicies() *ACLPolicies {
	return &ACLPolicies{client: c}
}

// List is used to dump all of the policies.
func (a *ACLPolicies) List(q *QueryOptions) ([]*ACLPolicyListStub, *QueryMeta, error) {
	var resp []*ACLPolicyListStub
	qm, err := a.client.query("/v1/acl/policies", &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return resp, qm, nil
}

// Upsert is used to create or update a policy
func (a *ACLPolicies) Upsert(policy *ACLPolicy, q *WriteOptions) (*WriteMeta, error) {
	if policy == nil || policy.Name == "" {
		return nil, errors.New("missing policy name")
	}
	wm, err := a.client.put("/v1/acl/policy/"+policy.Name, policy, nil, q)
	if err != nil {
		return nil, err
	}
	return wm, nil
}

// Delete is used to delete a policy
func (a *ACLPolicies) Delete(policyName string, q *WriteOptions) (*WriteMeta, error) {
	if policyName == "" {
		return nil, errors.New("missing policy name")
	}
	wm, err := a.client.delete("/v1/acl/policy/"+policyName, nil, nil, q)
	if err != nil {
		return nil, err
	}
	return wm, nil
}

// Info is used to query a specific policy
func (a *ACLPolicies) Info(policyName string, q *QueryOptions) (*ACLPolicy, *QueryMeta, error) {
	if policyName == "" {
		return nil, nil, errors.New("missing policy name")
	}
	var resp ACLPolicy
	wm, err := a.client.query("/v1/acl/policy/"+policyName, &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return &resp, wm, nil
}

// ACLTokens is used to query the ACL token endpoints.
type ACLTokens struct {
	client *Client
}

// ACLTokens returns a new handle on the ACL tokens.
func (c *Client) ACLTokens() *ACLTokens {
	return &ACLTokens{client: c}
}

// DEPRECATED: will be removed in Nomad 1.5.0
// Bootstrap is used to get the initial bootstrap token
func (a *ACLTokens) Bootstrap(q *WriteOptions) (*ACLToken, *WriteMeta, error) {
	var resp ACLToken
	wm, err := a.client.put("/v1/acl/bootstrap", nil, &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return &resp, wm, nil
}

// BootstrapOpts is used to get the initial bootstrap token or pass in the one that was provided in the API
func (a *ACLTokens) BootstrapOpts(btoken string, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
	if q == nil {
		q = &WriteOptions{}
	}
	req := &BootstrapRequest{
		BootstrapSecret: btoken,
	}

	var resp ACLToken
	wm, err := a.client.put("/v1/acl/bootstrap", req, &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return &resp, wm, nil
}

// List is used to dump all of the tokens.
func (a *ACLTokens) List(q *QueryOptions) ([]*ACLTokenListStub, *QueryMeta, error) {
	var resp []*ACLTokenListStub
	qm, err := a.client.query("/v1/acl/tokens", &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return resp, qm, nil
}

// Create is used to create a token
func (a *ACLTokens) Create(token *ACLToken, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
	if token.AccessorID != "" {
		return nil, nil, errors.New("cannot specify Accessor ID")
	}
	var resp ACLToken
	wm, err := a.client.put("/v1/acl/token", token, &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return &resp, wm, nil
}

// Update is used to update an existing token
func (a *ACLTokens) Update(token *ACLToken, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
	if token.AccessorID == "" {
		return nil, nil, errors.New("missing accessor ID")
	}
	var resp ACLToken
	wm, err := a.client.put("/v1/acl/token/"+token.AccessorID,
		token, &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return &resp, wm, nil
}

// Delete is used to delete a token
func (a *ACLTokens) Delete(accessorID string, q *WriteOptions) (*WriteMeta, error) {
	if accessorID == "" {
		return nil, errors.New("missing accessor ID")
	}
	wm, err := a.client.delete("/v1/acl/token/"+accessorID, nil, nil, q)
	if err != nil {
		return nil, err
	}
	return wm, nil
}

// Info is used to query a token
func (a *ACLTokens) Info(accessorID string, q *QueryOptions) (*ACLToken, *QueryMeta, error) {
	if accessorID == "" {
		return nil, nil, errors.New("missing accessor ID")
	}
	var resp ACLToken
	wm, err := a.client.query("/v1/acl/token/"+accessorID, &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return &resp, wm, nil
}

// Self is used to query our own token
func (a *ACLTokens) Self(q *QueryOptions) (*ACLToken, *QueryMeta, error) {
	var resp ACLToken
	wm, err := a.client.query("/v1/acl/token/self", &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return &resp, wm, nil
}

// UpsertOneTimeToken is used to create a one-time token
func (a *ACLTokens) UpsertOneTimeToken(q *WriteOptions) (*OneTimeToken, *WriteMeta, error) {
	var resp *OneTimeTokenUpsertResponse
	wm, err := a.client.put("/v1/acl/token/onetime", nil, &resp, q)
	if err != nil {
		return nil, nil, err
	}
	if resp == nil {
		return nil, nil, errors.New("no one-time token returned")
	}
	return resp.OneTimeToken, wm, nil
}

// ExchangeOneTimeToken is used to create a one-time token
func (a *ACLTokens) ExchangeOneTimeToken(secret string, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
	if secret == "" {
		return nil, nil, errors.New("missing secret ID")
	}
	req := &OneTimeTokenExchangeRequest{OneTimeSecretID: secret}
	var resp *OneTimeTokenExchangeResponse
	wm, err := a.client.put("/v1/acl/token/onetime/exchange", req, &resp, q)
	if err != nil {
		return nil, nil, err
	}
	if resp == nil {
		return nil, nil, errors.New("no ACL token returned")
	}
	return resp.Token, wm, nil
}

var (
	// errMissingACLRoleID is the generic errors to use when a call is missing
	// the required ACL Role ID parameter.
	errMissingACLRoleID = errors.New("missing ACL role ID")

	// errMissingACLAuthMethodName is the generic error to use when a call is
	// missing the required ACL auth-method name parameter.
	errMissingACLAuthMethodName = errors.New("missing ACL auth-method name")

	// errMissingACLBindingRuleID is the generic error to use when a call is
	// missing the required ACL binding rule ID parameter.
	errMissingACLBindingRuleID = errors.New("missing ACL binding rule ID")
)

// ACLRoles is used to query the ACL Role endpoints.
type ACLRoles struct {
	client *Client
}

// ACLRoles returns a new handle on the ACL roles API client.
func (c *Client) ACLRoles() *ACLRoles {
	return &ACLRoles{client: c}
}

// List is used to detail all the ACL roles currently stored within state.
func (a *ACLRoles) List(q *QueryOptions) ([]*ACLRoleListStub, *QueryMeta, error) {
	var resp []*ACLRoleListStub
	qm, err := a.client.query("/v1/acl/roles", &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return resp, qm, nil
}

// Create is used to create an ACL role.
func (a *ACLRoles) Create(role *ACLRole, w *WriteOptions) (*ACLRole, *WriteMeta, error) {
	if role.ID != "" {
		return nil, nil, errors.New("cannot specify ACL role ID")
	}
	var resp ACLRole
	wm, err := a.client.put("/v1/acl/role", role, &resp, w)
	if err != nil {
		return nil, nil, err
	}
	return &resp, wm, nil
}

// Update is used to update an existing ACL role.
func (a *ACLRoles) Update(role *ACLRole, w *WriteOptions) (*ACLRole, *WriteMeta, error) {
	if role.ID == "" {
		return nil, nil, errMissingACLRoleID
	}
	var resp ACLRole
	wm, err := a.client.put("/v1/acl/role/"+role.ID, role, &resp, w)
	if err != nil {
		return nil, nil, err
	}
	return &resp, wm, nil
}

// Delete is used to delete an ACL role.
func (a *ACLRoles) Delete(roleID string, w *WriteOptions) (*WriteMeta, error) {
	if roleID == "" {
		return nil, errMissingACLRoleID
	}
	wm, err := a.client.delete("/v1/acl/role/"+roleID, nil, nil, w)
	if err != nil {
		return nil, err
	}
	return wm, nil
}

// Get is used to look up an ACL role.
func (a *ACLRoles) Get(roleID string, q *QueryOptions) (*ACLRole, *QueryMeta, error) {
	if roleID == "" {
		return nil, nil, errMissingACLRoleID
	}
	var resp ACLRole
	qm, err := a.client.query("/v1/acl/role/"+roleID, &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return &resp, qm, nil
}

// GetByName is used to look up an ACL role using its name.
func (a *ACLRoles) GetByName(roleName string, q *QueryOptions) (*ACLRole, *QueryMeta, error) {
	if roleName == "" {
		return nil, nil, errors.New("missing ACL role name")
	}
	var resp ACLRole
	qm, err := a.client.query("/v1/acl/role/name/"+roleName, &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return &resp, qm, nil
}

// ACLAuthMethods is used to query the ACL auth-methods endpoints.
type ACLAuthMethods struct {
	client *Client
}

// ACLAuthMethods returns a new handle on the ACL auth-methods API client.
func (c *Client) ACLAuthMethods() *ACLAuthMethods {
	return &ACLAuthMethods{client: c}
}

// List is used to detail all the ACL auth-methods currently stored within
// state.
func (a *ACLAuthMethods) List(q *QueryOptions) ([]*ACLAuthMethodListStub, *QueryMeta, error) {
	var resp []*ACLAuthMethodListStub
	qm, err := a.client.query("/v1/acl/auth-methods", &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return resp, qm, nil
}

// Create is used to create an ACL auth-method.
func (a *ACLAuthMethods) Create(authMethod *ACLAuthMethod, w *WriteOptions) (*ACLAuthMethod, *WriteMeta, error) {
	if authMethod.Name == "" {
		return nil, nil, errMissingACLAuthMethodName
	}
	var resp ACLAuthMethod
	wm, err := a.client.put("/v1/acl/auth-method", authMethod, &resp, w)
	if err != nil {
		return nil, nil, err
	}
	return &resp, wm, nil
}

// Update is used to update an existing ACL auth-method.
func (a *ACLAuthMethods) Update(authMethod *ACLAuthMethod, w *WriteOptions) (*ACLAuthMethod, *WriteMeta, error) {
	if authMethod.Name == "" {
		return nil, nil, errMissingACLAuthMethodName
	}
	var resp ACLAuthMethod
	wm, err := a.client.put("/v1/acl/auth-method/"+authMethod.Name, authMethod, &resp, w)
	if err != nil {
		return nil, nil, err
	}
	return &resp, wm, nil
}

// Delete is used to delete an ACL auth-method.
func (a *ACLAuthMethods) Delete(authMethodName string, w *WriteOptions) (*WriteMeta, error) {
	if authMethodName == "" {
		return nil, errMissingACLAuthMethodName
	}
	wm, err := a.client.delete("/v1/acl/auth-method/"+authMethodName, nil, nil, w)
	if err != nil {
		return nil, err
	}
	return wm, nil
}

// Get is used to look up an ACL auth-method.
func (a *ACLAuthMethods) Get(authMethodName string, q *QueryOptions) (*ACLAuthMethod, *QueryMeta, error) {
	if authMethodName == "" {
		return nil, nil, errMissingACLAuthMethodName
	}
	var resp ACLAuthMethod
	qm, err := a.client.query("/v1/acl/auth-method/"+authMethodName, &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return &resp, qm, nil
}

// ACLBindingRules is used to query the ACL auth-methods endpoints.
type ACLBindingRules struct {
	client *Client
}

// ACLBindingRules returns a new handle on the ACL auth-methods API client.
func (c *Client) ACLBindingRules() *ACLBindingRules {
	return &ACLBindingRules{client: c}
}

// List is used to detail all the ACL binding rules currently stored within
// state.
func (a *ACLBindingRules) List(q *QueryOptions) ([]*ACLBindingRuleListStub, *QueryMeta, error) {
	var resp []*ACLBindingRuleListStub
	qm, err := a.client.query("/v1/acl/binding-rules", &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return resp, qm, nil
}

// Create is used to create an ACL binding rule.
func (a *ACLBindingRules) Create(bindingRule *ACLBindingRule, w *WriteOptions) (*ACLBindingRule, *WriteMeta, error) {
	var resp ACLBindingRule
	wm, err := a.client.put("/v1/acl/binding-rule", bindingRule, &resp, w)
	if err != nil {
		return nil, nil, err
	}
	return &resp, wm, nil
}

// Update is used to update an existing ACL binding rule.
func (a *ACLBindingRules) Update(bindingRule *ACLBindingRule, w *WriteOptions) (*ACLBindingRule, *WriteMeta, error) {
	if bindingRule.ID == "" {
		return nil, nil, errMissingACLBindingRuleID
	}
	var resp ACLBindingRule
	wm, err := a.client.put("/v1/acl/binding-rule/"+bindingRule.ID, bindingRule, &resp, w)
	if err != nil {
		return nil, nil, err
	}
	return &resp, wm, nil
}

// Delete is used to delete an ACL binding rule.
func (a *ACLBindingRules) Delete(bindingRuleID string, w *WriteOptions) (*WriteMeta, error) {
	if bindingRuleID == "" {
		return nil, errMissingACLBindingRuleID
	}
	wm, err := a.client.delete("/v1/acl/binding-rule/"+bindingRuleID, nil, nil, w)
	if err != nil {
		return nil, err
	}
	return wm, nil
}

// Get is used to look up an ACL binding rule.
func (a *ACLBindingRules) Get(bindingRuleID string, q *QueryOptions) (*ACLBindingRule, *QueryMeta, error) {
	if bindingRuleID == "" {
		return nil, nil, errMissingACLBindingRuleID
	}
	var resp ACLBindingRule
	qm, err := a.client.query("/v1/acl/binding-rule/"+bindingRuleID, &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return &resp, qm, nil
}

// ACLOIDC is used to query the ACL OIDC endpoints.
//
// Deprecated: ACLOIDC is deprecated, use ACLAuth instead.
type ACLOIDC struct {
	client *Client
	ACLAuth
}

// ACLOIDC returns a new handle on the ACL auth-methods API client.
//
// Deprecated: c.ACLOIDC() is deprecated, use c.ACLAuth() instead.
func (c *Client) ACLOIDC() *ACLOIDC {
	return &ACLOIDC{client: c}
}

// ACLAuth is used to query the ACL auth endpoints.
type ACLAuth struct {
	client *Client
}

// ACLAuth returns a new handle on the ACL auth-methods API client.
func (c *Client) ACLAuth() *ACLAuth {
	return &ACLAuth{client: c}
}

// GetAuthURL generates the OIDC provider authentication URL. This URL should
// be visited in order to sign in to the provider.
func (a *ACLAuth) GetAuthURL(req *ACLOIDCAuthURLRequest, q *WriteOptions) (*ACLOIDCAuthURLResponse, *WriteMeta, error) {
	var resp ACLOIDCAuthURLResponse
	wm, err := a.client.put("/v1/acl/oidc/auth-url", req, &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return &resp, wm, nil
}

// CompleteAuth exchanges the OIDC provider token for a Nomad token with the
// appropriate claims attached.
func (a *ACLAuth) CompleteAuth(req *ACLOIDCCompleteAuthRequest, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
	var resp ACLToken
	wm, err := a.client.put("/v1/acl/oidc/complete-auth", req, &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return &resp, wm, nil
}

// Login exchanges the third party token for a Nomad token with the appropriate
// claims attached.
func (a *ACLAuth) Login(req *ACLLoginRequest, q *WriteOptions) (*ACLToken, *WriteMeta, error) {
	var resp ACLToken
	wm, err := a.client.put("/v1/acl/login", req, &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return &resp, wm, nil
}

// ACLPolicyListStub is used to for listing ACL policies
type ACLPolicyListStub struct {
	Name        string
	Description string
	CreateIndex uint64
	ModifyIndex uint64
}

// ACLPolicy is used to represent an ACL policy
type ACLPolicy struct {
	Name        string
	Description string
	Rules       string
	JobACL      *JobACL

	CreateIndex uint64
	ModifyIndex uint64
}

// JobACL represents an ACL policy's attachment to a job, group, or task.
type JobACL struct {
	Namespace string
	JobID     string
	Group     string
	Task      string
}

// ACLToken represents a client token which is used to Authenticate
type ACLToken struct {
	AccessorID string
	SecretID   string
	Name       string
	Type       string
	Policies   []string

	// Roles represents the ACL roles that this token is tied to. The token
	// will inherit the permissions of all policies detailed within the role.
	Roles []*ACLTokenRoleLink

	Global     bool
	CreateTime time.Time

	// ExpirationTime represents the point after which a token should be
	// considered revoked and is eligible for destruction. The zero value of
	// time.Time does not respect json omitempty directives, so we must use a
	// pointer.
	ExpirationTime *time.Time `json:",omitempty"`

	// ExpirationTTL is a convenience field for helping set ExpirationTime to a
	// value of CreateTime+ExpirationTTL. This can only be set during token
	// creation. This is a string version of a time.Duration like "2m".
	ExpirationTTL time.Duration `json:",omitempty"`

	CreateIndex uint64
	ModifyIndex uint64
}

// ACLTokenRoleLink is used to link an ACL token to an ACL role. The ACL token
// can therefore inherit all the ACL policy permissions that the ACL role
// contains.
type ACLTokenRoleLink struct {

	// ID is the ACLRole.ID UUID. This field is immutable and represents the
	// absolute truth for the link.
	ID string

	// Name is the human friendly identifier for the ACL role and is a
	// convenience field for operators.
	Name string
}

// MarshalJSON implements the json.Marshaler interface and allows
// ACLToken.ExpirationTTL to be marshaled correctly.
func (a *ACLToken) MarshalJSON() ([]byte, error) {
	type Alias ACLToken
	exported := &struct {
		ExpirationTTL string
		*Alias
	}{
		ExpirationTTL: a.ExpirationTTL.String(),
		Alias:         (*Alias)(a),
	}
	if a.ExpirationTTL == 0 {
		exported.ExpirationTTL = ""
	}
	return json.Marshal(exported)
}

// UnmarshalJSON implements the json.Unmarshaler interface and allows
// ACLToken.ExpirationTTL to be unmarshalled correctly.
func (a *ACLToken) UnmarshalJSON(data []byte) (err error) {
	type Alias ACLToken
	aux := &struct {
		ExpirationTTL any
		*Alias
	}{
		Alias: (*Alias)(a),
	}

	if err = json.Unmarshal(data, &aux); err != nil {
		return err
	}
	if aux.ExpirationTTL != nil {
		switch v := aux.ExpirationTTL.(type) {
		case string:
			if v != "" {
				if a.ExpirationTTL, err = time.ParseDuration(v); err != nil {
					return err
				}
			}
		case float64:
			a.ExpirationTTL = time.Duration(v)
		}

	}
	return nil
}

type ACLTokenListStub struct {
	AccessorID string
	Name       string
	Type       string
	Policies   []string
	Roles      []*ACLTokenRoleLink
	Global     bool
	CreateTime time.Time

	// ExpirationTime represents the point after which a token should be
	// considered revoked and is eligible for destruction. A nil value
	// indicates no expiration has been set on the token.
	ExpirationTime *time.Time `json:",omitempty"`

	CreateIndex uint64
	ModifyIndex uint64
}

type OneTimeToken struct {
	OneTimeSecretID string
	AccessorID      string
	ExpiresAt       time.Time
	CreateIndex     uint64
	ModifyIndex     uint64
}

type OneTimeTokenUpsertResponse struct {
	OneTimeToken *OneTimeToken
}

type OneTimeTokenExchangeRequest struct {
	OneTimeSecretID string
}

type OneTimeTokenExchangeResponse struct {
	Token *ACLToken
}

// BootstrapRequest is used for when operators provide an ACL Bootstrap Token
type BootstrapRequest struct {
	BootstrapSecret string
}

// ACLRole is an abstraction for the ACL system which allows the grouping of
// ACL policies into a single object. ACL tokens can be created and linked to
// a role; the token then inherits all the permissions granted by the policies.
type ACLRole struct {

	// ID is an internally generated UUID for this role and is controlled by
	// Nomad. It can be used after role creation to update the existing role.
	ID string

	// Name is unique across the entire set of federated clusters and is
	// supplied by the operator on role creation. The name can be modified by
	// updating the role and including the Nomad generated ID. This update will
	// not affect tokens created and linked to this role. This is a required
	// field.
	Name string

	// Description is a human-readable, operator set description that can
	// provide additional context about the role. This is an optional field.
	Description string

	// Policies is an array of ACL policy links. Although currently policies
	// can only be linked using their name, in the future we will want to add
	// IDs also and thus allow operators to specify either a name, an ID, or
	// both. At least one entry is required.
	Policies []*ACLRolePolicyLink

	CreateIndex uint64
	ModifyIndex uint64
}

// ACLRolePolicyLink is used to link a policy to an ACL role. We use a struct
// rather than a list of strings as in the future we will want to add IDs to
// policies and then link via these.
type ACLRolePolicyLink struct {

	// Name is the ACLPolicy.Name value which will be linked to the ACL role.
	Name string
}

// ACLRoleListStub is the stub object returned when performing a listing of ACL
// roles. While it might not currently be different to the full response
// object, it allows us to future-proof the RPC in the event the ACLRole object
// grows over time.
type ACLRoleListStub struct {

	// ID is an internally generated UUID for this role and is controlled by
	// Nomad.
	ID string

	// Name is unique across the entire set of federated clusters and is
	// supplied by the operator on role creation. The name can be modified by
	// updating the role and including the Nomad generated ID. This update will
	// not affect tokens created and linked to this role. This is a required
	// field.
	Name string

	// Description is a human-readable, operator set description that can
	// provide additional context about the role. This is an operational field.
	Description string

	// Policies is an array of ACL policy links. Although currently policies
	// can only be linked using their name, in the future we will want to add
	// IDs also and thus allow operators to specify either a name, an ID, or
	// both.
	Policies []*ACLRolePolicyLink

	CreateIndex uint64
	ModifyIndex uint64
}

// ACLAuthMethod is used to capture the properties of an authentication method
// used for single sing-on.
type ACLAuthMethod struct {

	// Name is the identifier for this auth-method and is a required parameter.
	Name string

	// Type is the SSO identifier this auth-method is. Nomad currently only
	// supports "oidc" and the API contains ACLAuthMethodTypeOIDC for
	// convenience.
	Type string

	// Defines whether the auth-method creates a local or global token when
	// performing SSO login. This should be set to either "local" or "global"
	// and the API contains ACLAuthMethodTokenLocalityLocal and
	// ACLAuthMethodTokenLocalityGlobal for convenience.
	TokenLocality string

	// MaxTokenTTL is the maximum life of a token created by this method.
	MaxTokenTTL time.Duration

	// Default identifies whether this is the default auth-method to use when
	// attempting to login without specifying an auth-method name to use.
	Default bool

	// Config contains the detailed configuration which is specific to the
	// auth-method.
	Config *ACLAuthMethodConfig

	CreateTime  time.Time
	ModifyTime  time.Time
	CreateIndex uint64
	ModifyIndex uint64
}

// MarshalJSON implements the json.Marshaler interface and allows
// ACLAuthMethod.MaxTokenTTL to be marshaled correctly.
func (m *ACLAuthMethod) MarshalJSON() ([]byte, error) {
	type Alias ACLAuthMethod
	exported := &struct {
		MaxTokenTTL string
		*Alias
	}{
		MaxTokenTTL: m.MaxTokenTTL.String(),
		Alias:       (*Alias)(m),
	}
	if m.MaxTokenTTL == 0 {
		exported.MaxTokenTTL = ""
	}
	return json.Marshal(exported)
}

// UnmarshalJSON implements the json.Unmarshaler interface and allows
// ACLAuthMethod.MaxTokenTTL to be unmarshalled correctly.
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
}

// ACLAuthMethodConfig is used to store configuration of an auth method.
type ACLAuthMethodConfig struct {
	// A list of PEM-encoded public keys to use to authenticate signatures
	// locally
	JWTValidationPubKeys []string
	// JSON Web Key Sets url for authenticating signatures
	JWKSURL string
	// The OIDC Discovery URL, without any .well-known component (base path)
	OIDCDiscoveryURL string
	// The OAuth Client ID configured with the OIDC provider
	OIDCClientID string
	// The OAuth Client Secret configured with the OIDC provider
	OIDCClientSecret string
	// List of OIDC scopes
	OIDCScopes []string
	// List of auth claims that are valid for login
	BoundAudiences []string
	// The value against which to match the iss claim in a JWT
	BoundIssuer []string
	// A list of allowed values for redirect_uri
	AllowedRedirectURIs []string
	// PEM encoded CA certs for use by the TLS client used to talk with the
	// OIDC Discovery URL.
	DiscoveryCaPem []string
	// PEM encoded CA cert for use by the TLS client used to talk with the JWKS
	// URL
	JWKSCACert string
	// A list of supported signing algorithms
	SigningAlgs []string
	// Duration in seconds of leeway when validating expiration of a token to
	// account for clock skew
	ExpirationLeeway time.Duration
	// Duration in seconds of leeway when validating not before values of a
	// token to account for clock skew.
	NotBeforeLeeway time.Duration
	// Duration in seconds of leeway when validating all claims to account for
	// clock skew.
	ClockSkewLeeway time.Duration
	// Mappings of claims (key) that will be copied to a metadata field
	// (value).
	ClaimMappings     map[string]string
	ListClaimMappings map[string]string
}

// MarshalJSON implements the json.Marshaler interface and allows
// time.Duration fields to be marshaled correctly.
func (c *ACLAuthMethodConfig) MarshalJSON() ([]byte, error) {
	type Alias ACLAuthMethodConfig
	exported := &struct {
		ExpirationLeeway string
		NotBeforeLeeway  string
		ClockSkewLeeway  string
		*Alias
	}{
		ExpirationLeeway: c.ExpirationLeeway.String(),
		NotBeforeLeeway:  c.NotBeforeLeeway.String(),
		ClockSkewLeeway:  c.ClockSkewLeeway.String(),
		Alias:            (*Alias)(c),
	}
	if c.ExpirationLeeway == 0 {
		exported.ExpirationLeeway = ""
	}
	if c.NotBeforeLeeway == 0 {
		exported.NotBeforeLeeway = ""
	}
	if c.ClockSkewLeeway == 0 {
		exported.ClockSkewLeeway = ""
	}
	return json.Marshal(exported)
}

// UnmarshalJSON implements the json.Unmarshaler interface and allows
// time.Duration fields to be unmarshalled correctly.
func (c *ACLAuthMethodConfig) UnmarshalJSON(data []byte) error {
	type Alias ACLAuthMethodConfig
	aux := &struct {
		ExpirationLeeway any
		NotBeforeLeeway  any
		ClockSkewLeeway  any
		*Alias
	}{
		Alias: (*Alias)(c),
	}
	if err := json.Unmarshal(data, &aux); err != nil {
		return err
	}
	var err error
	if aux.ExpirationLeeway != nil {
		switch v := aux.ExpirationLeeway.(type) {
		case string:
			if v != "" {
				if c.ExpirationLeeway, err = time.ParseDuration(v); err != nil {
					return err
				}
			}
		case float64:
			c.ExpirationLeeway = time.Duration(v)
		default:
			return fmt.Errorf("unexpected ExpirationLeeway type: %v", v)
		}
	}
	if aux.NotBeforeLeeway != nil {
		switch v := aux.NotBeforeLeeway.(type) {
		case string:
			if v != "" {
				if c.NotBeforeLeeway, err = time.ParseDuration(v); err != nil {
					return err
				}
			}
		case float64:
			c.NotBeforeLeeway = time.Duration(v)
		default:
			return fmt.Errorf("unexpected NotBeforeLeeway type: %v", v)
		}
	}
	if aux.ClockSkewLeeway != nil {
		switch v := aux.ClockSkewLeeway.(type) {
		case string:
			if v != "" {
				if c.ClockSkewLeeway, err = time.ParseDuration(v); err != nil {
					return err
				}
			}
		case float64:
			c.ClockSkewLeeway = time.Duration(v)
		default:
			return fmt.Errorf("unexpected ClockSkewLeeway type: %v", v)
		}
	}
	return nil
}

// ACLAuthMethodListStub is the stub object returned when performing a listing
// of ACL auth-methods. It is intentionally minimal due to the unauthenticated
// nature of the list endpoint.
type ACLAuthMethodListStub struct {
	Name    string
	Type    string
	Default bool

	CreateIndex uint64
	ModifyIndex uint64
}

const (
	// ACLAuthMethodTokenLocalityLocal is the ACLAuthMethod.TokenLocality that
	// will generate ACL tokens which can only be used on the local cluster the
	// request was made.
	ACLAuthMethodTokenLocalityLocal = "local"

	// ACLAuthMethodTokenLocalityGlobal is the ACLAuthMethod.TokenLocality that
	// will generate ACL tokens which can be used on all federated clusters.
	ACLAuthMethodTokenLocalityGlobal = "global"

	// ACLAuthMethodTypeOIDC the ACLAuthMethod.Type and represents an
	// auth-method which uses the OIDC protocol.
	ACLAuthMethodTypeOIDC = "OIDC"

	// ACLAuthMethodTypeJWT the ACLAuthMethod.Type and represents an auth-method
	// which uses the JWT type.
	ACLAuthMethodTypeJWT = "JWT"
)

// ACLBindingRule contains a direct relation to an ACLAuthMethod and represents
// a rule to apply when logging in via the named AuthMethod. This allows the
// transformation of OIDC provider claims, to Nomad based ACL concepts such as
// ACL Roles and Policies.
type ACLBindingRule struct {

	// ID is an internally generated UUID for this rule and is controlled by
	// Nomad.
	ID string

	// Description is a human-readable, operator set description that can
	// provide additional context about the binding rule. This is an
	// operational field.
	Description string

	// AuthMethod is the name of the auth method for which this rule applies
	// to. This is required and the method must exist within state before the
	// cluster administrator can create the rule.
	AuthMethod string

	// Selector is an expression that matches against verified identity
	// attributes returned from the auth method during login. This is optional
	// and when not set, provides a catch-all rule.
	Selector string

	// BindType adjusts how this binding rule is applied at login time. The
	// valid values are ACLBindingRuleBindTypeRole,
	// ACLBindingRuleBindTypePolicy, and ACLBindingRuleBindTypeManagement.
	BindType string

	// BindName is the target of the binding. Can be lightly templated using
	// HIL ${foo} syntax from available field names. How it is used depends
	// upon the BindType.
	BindName string

	CreateTime  time.Time
	ModifyTime  time.Time
	CreateIndex uint64
	ModifyIndex uint64
}

const (
	// ACLBindingRuleBindTypeRole is the ACL binding rule bind type that only
	// allows the binding rule to function if a role exists at login-time. The
	// role will be specified within the ACLBindingRule.BindName parameter, and
	// will identify whether this is an ID or Name.
	ACLBindingRuleBindTypeRole = "role"

	// ACLBindingRuleBindTypePolicy is the ACL binding rule bind type that
	// assigns a policy to the generate ACL token. The role will be specified
	// within the ACLBindingRule.BindName parameter, and will be the policy
	// name.
	ACLBindingRuleBindTypePolicy = "policy"

	// ACLBindingRuleBindTypeManagement is the ACL binding rule bind type that
	// will generate management ACL tokens when matched.
	ACLBindingRuleBindTypeManagement = "management"
)

// ACLBindingRuleListStub is the stub object returned when performing a listing
// of ACL binding rules.
type ACLBindingRuleListStub struct {

	// ID is an internally generated UUID for this role and is controlled by
	// Nomad.
	ID string

	// Description is a human-readable, operator set description that can
	// provide additional context about the binding role. This is an
	// operational field.
	Description string

	// AuthMethod is the name of the auth method for which this rule applies
	// to. This is required and the method must exist within state before the
	// cluster administrator can create the rule.
	AuthMethod string

	CreateIndex uint64
	ModifyIndex uint64
}

// ACLOIDCAuthURLRequest is the request to make when starting the OIDC
// authentication login flow.
type ACLOIDCAuthURLRequest struct {

	// AuthMethodName is the OIDC auth-method to use. This is a required
	// parameter.
	AuthMethodName string

	// RedirectURI is the URL that authorization should redirect to. This is a
	// required parameter.
	RedirectURI string

	// ClientNonce is a randomly generated string to prevent replay attacks. It
	// is up to the client to generate this and Go integrations should use the
	// oidc.NewID function within the hashicorp/cap library.
	ClientNonce string
}

// ACLOIDCAuthURLResponse is the response when starting the OIDC authentication
// login flow.
type ACLOIDCAuthURLResponse struct {

	// AuthURL is URL to begin authorization and is where the user logging in
	// should go.
	AuthURL string
}

// ACLOIDCCompleteAuthRequest is the request object to begin completing the
// OIDC auth cycle after receiving the callback from the OIDC provider.
type ACLOIDCCompleteAuthRequest struct {

	// AuthMethodName is the name of the auth method being used to login via
	// OIDC. This will match AuthUrlArgs.AuthMethodName. This is a required
	// parameter.
	AuthMethodName string

	// ClientNonce, State, and Code are provided from the parameters given to
	// the redirect URL. These are all required parameters.
	ClientNonce string
	State       string
	Code        string

	// RedirectURI is the URL that authorization should redirect to. This is a
	// required parameter.
	RedirectURI string
}

// ACLLoginRequest is the request object to begin auth with an external bearer
// token provider.
type ACLLoginRequest struct {
	// AuthMethodName is the name of the auth method being used to login. This
	// is a required parameter.
	AuthMethodName string
	// LoginToken is the token used to login. This is a required parameter.
	LoginToken string
}
