package kubernetes

import (
	"strings"
	"time"

	jose "gopkg.in/square/go-jose.v2"

	"github.com/dexidp/dex/storage"
	"github.com/dexidp/dex/storage/kubernetes/k8sapi"
)

var crdMeta = k8sapi.TypeMeta{
	APIVersion: "apiextensions.k8s.io/v1beta1",
	Kind:       "CustomResourceDefinition",
}

const apiGroup = "dex.coreos.com"

// The set of custom resource definitions required by the storage. These are managed by
// the storage so it can migrate itself by creating new resources.
var customResourceDefinitions = []k8sapi.CustomResourceDefinition{
	{
		ObjectMeta: k8sapi.ObjectMeta{
			Name: "authcodes.dex.coreos.com",
		},
		TypeMeta: crdMeta,
		Spec: k8sapi.CustomResourceDefinitionSpec{
			Group:   apiGroup,
			Version: "v1",
			Names: k8sapi.CustomResourceDefinitionNames{
				Plural:   "authcodes",
				Singular: "authcode",
				Kind:     "AuthCode",
			},
		},
	},
	{
		ObjectMeta: k8sapi.ObjectMeta{
			Name: "authrequests.dex.coreos.com",
		},
		TypeMeta: crdMeta,
		Spec: k8sapi.CustomResourceDefinitionSpec{
			Group:   apiGroup,
			Version: "v1",
			Names: k8sapi.CustomResourceDefinitionNames{
				Plural:   "authrequests",
				Singular: "authrequest",
				Kind:     "AuthRequest",
			},
		},
	},
	{
		ObjectMeta: k8sapi.ObjectMeta{
			Name: "oauth2clients.dex.coreos.com",
		},
		TypeMeta: crdMeta,
		Spec: k8sapi.CustomResourceDefinitionSpec{
			Group:   apiGroup,
			Version: "v1",
			Names: k8sapi.CustomResourceDefinitionNames{
				Plural:   "oauth2clients",
				Singular: "oauth2client",
				Kind:     "OAuth2Client",
			},
		},
	},
	{
		ObjectMeta: k8sapi.ObjectMeta{
			Name: "signingkeies.dex.coreos.com",
		},
		TypeMeta: crdMeta,
		Spec: k8sapi.CustomResourceDefinitionSpec{
			Group:   apiGroup,
			Version: "v1",
			Names: k8sapi.CustomResourceDefinitionNames{
				// `signingkeies` is an artifact from the old TPR pluralization.
				// Users don't directly interact with this value, hence leaving it
				// as is.
				Plural:   "signingkeies",
				Singular: "signingkey",
				Kind:     "SigningKey",
			},
		},
	},
	{
		ObjectMeta: k8sapi.ObjectMeta{
			Name: "refreshtokens.dex.coreos.com",
		},
		TypeMeta: crdMeta,
		Spec: k8sapi.CustomResourceDefinitionSpec{
			Group:   apiGroup,
			Version: "v1",
			Names: k8sapi.CustomResourceDefinitionNames{
				Plural:   "refreshtokens",
				Singular: "refreshtoken",
				Kind:     "RefreshToken",
			},
		},
	},
	{
		ObjectMeta: k8sapi.ObjectMeta{
			Name: "passwords.dex.coreos.com",
		},
		TypeMeta: crdMeta,
		Spec: k8sapi.CustomResourceDefinitionSpec{
			Group:   apiGroup,
			Version: "v1",
			Names: k8sapi.CustomResourceDefinitionNames{
				Plural:   "passwords",
				Singular: "password",
				Kind:     "Password",
			},
		},
	},
	{
		ObjectMeta: k8sapi.ObjectMeta{
			Name: "offlinesessionses.dex.coreos.com",
		},
		TypeMeta: crdMeta,
		Spec: k8sapi.CustomResourceDefinitionSpec{
			Group:   apiGroup,
			Version: "v1",
			Names: k8sapi.CustomResourceDefinitionNames{
				Plural:   "offlinesessionses",
				Singular: "offlinesessions",
				Kind:     "OfflineSessions",
			},
		},
	},
	{
		ObjectMeta: k8sapi.ObjectMeta{
			Name: "connectors.dex.coreos.com",
		},
		TypeMeta: crdMeta,
		Spec: k8sapi.CustomResourceDefinitionSpec{
			Group:   apiGroup,
			Version: "v1",
			Names: k8sapi.CustomResourceDefinitionNames{
				Plural:   "connectors",
				Singular: "connector",
				Kind:     "Connector",
			},
		},
	},
	{
		ObjectMeta: k8sapi.ObjectMeta{
			Name: "devicerequests.dex.coreos.com",
		},
		TypeMeta: crdMeta,
		Spec: k8sapi.CustomResourceDefinitionSpec{
			Group:   apiGroup,
			Version: "v1",
			Names: k8sapi.CustomResourceDefinitionNames{
				Plural:   "devicerequests",
				Singular: "devicerequest",
				Kind:     "DeviceRequest",
			},
		},
	},
	{
		ObjectMeta: k8sapi.ObjectMeta{
			Name: "devicetokens.dex.coreos.com",
		},
		TypeMeta: crdMeta,
		Spec: k8sapi.CustomResourceDefinitionSpec{
			Group:   apiGroup,
			Version: "v1",
			Names: k8sapi.CustomResourceDefinitionNames{
				Plural:   "devicetokens",
				Singular: "devicetoken",
				Kind:     "DeviceToken",
			},
		},
	},
}

// There will only ever be a single keys resource. Maintain this by setting a
// common name.
const keysName = "openid-connect-keys"

// Client is a mirrored struct from storage with JSON struct tags and
// Kubernetes type metadata.
type Client struct {
	// Name is a hash of the ID.
	k8sapi.TypeMeta   `json:",inline"`
	k8sapi.ObjectMeta `json:"metadata,omitempty"`

	// ID is immutable, since it's a primary key and should not be changed.
	ID string `json:"id,omitempty"`

	Secret       string   `json:"secret,omitempty"`
	RedirectURIs []string `json:"redirectURIs,omitempty"`
	TrustedPeers []string `json:"trustedPeers,omitempty"`

	Public bool `json:"public"`

	Name    string `json:"name,omitempty"`
	LogoURL string `json:"logoURL,omitempty"`
}

// ClientList is a list of Clients.
type ClientList struct {
	k8sapi.TypeMeta `json:",inline"`
	k8sapi.ListMeta `json:"metadata,omitempty"`
	Clients         []Client `json:"items"`
}

func (cli *client) fromStorageClient(c storage.Client) Client {
	return Client{
		TypeMeta: k8sapi.TypeMeta{
			Kind:       kindClient,
			APIVersion: cli.apiVersion,
		},
		ObjectMeta: k8sapi.ObjectMeta{
			Name:      cli.idToName(c.ID),
			Namespace: cli.namespace,
		},
		ID:           c.ID,
		Secret:       c.Secret,
		RedirectURIs: c.RedirectURIs,
		TrustedPeers: c.TrustedPeers,
		Public:       c.Public,
		Name:         c.Name,
		LogoURL:      c.LogoURL,
	}
}

func toStorageClient(c Client) storage.Client {
	return storage.Client{
		ID:           c.ID,
		Secret:       c.Secret,
		RedirectURIs: c.RedirectURIs,
		TrustedPeers: c.TrustedPeers,
		Public:       c.Public,
		Name:         c.Name,
		LogoURL:      c.LogoURL,
	}
}

// Claims is a mirrored struct from storage with JSON struct tags.
type Claims struct {
	UserID            string   `json:"userID"`
	Username          string   `json:"username"`
	PreferredUsername string   `json:"preferredUsername"`
	Email             string   `json:"email"`
	EmailVerified     bool     `json:"emailVerified"`
	Groups            []string `json:"groups,omitempty"`
}

func fromStorageClaims(i storage.Claims) Claims {
	return Claims{
		UserID:            i.UserID,
		Username:          i.Username,
		PreferredUsername: i.PreferredUsername,
		Email:             i.Email,
		EmailVerified:     i.EmailVerified,
		Groups:            i.Groups,
	}
}

func toStorageClaims(i Claims) storage.Claims {
	return storage.Claims{
		UserID:            i.UserID,
		Username:          i.Username,
		PreferredUsername: i.PreferredUsername,
		Email:             i.Email,
		EmailVerified:     i.EmailVerified,
		Groups:            i.Groups,
	}
}

// AuthRequest is a mirrored struct from storage with JSON struct tags and
// Kubernetes type metadata.
type AuthRequest struct {
	k8sapi.TypeMeta   `json:",inline"`
	k8sapi.ObjectMeta `json:"metadata,omitempty"`

	ClientID      string   `json:"clientID"`
	ResponseTypes []string `json:"responseTypes,omitempty"`
	Scopes        []string `json:"scopes,omitempty"`
	RedirectURI   string   `json:"redirectURI"`

	Nonce string `json:"nonce,omitempty"`
	State string `json:"state,omitempty"`

	// The client has indicated that the end user must be shown an approval prompt
	// on all requests. The server cannot cache their initial action for subsequent
	// attempts.
	ForceApprovalPrompt bool `json:"forceApprovalPrompt,omitempty"`

	LoggedIn bool `json:"loggedIn"`

	// The identity of the end user. Generally nil until the user authenticates
	// with a backend.
	Claims Claims `json:"claims,omitempty"`
	// The connector used to login the user. Set when the user authenticates.
	ConnectorID   string `json:"connectorID,omitempty"`
	ConnectorData []byte `json:"connectorData,omitempty"`

	Expiry time.Time `json:"expiry"`

	CodeChallenge       string `json:"code_challenge,omitempty"`
	CodeChallengeMethod string `json:"code_challenge_method,omitempty"`
}

// AuthRequestList is a list of AuthRequests.
type AuthRequestList struct {
	k8sapi.TypeMeta `json:",inline"`
	k8sapi.ListMeta `json:"metadata,omitempty"`
	AuthRequests    []AuthRequest `json:"items"`
}

func toStorageAuthRequest(req AuthRequest) storage.AuthRequest {
	a := storage.AuthRequest{
		ID:                  req.ObjectMeta.Name,
		ClientID:            req.ClientID,
		ResponseTypes:       req.ResponseTypes,
		Scopes:              req.Scopes,
		RedirectURI:         req.RedirectURI,
		Nonce:               req.Nonce,
		State:               req.State,
		ForceApprovalPrompt: req.ForceApprovalPrompt,
		LoggedIn:            req.LoggedIn,
		ConnectorID:         req.ConnectorID,
		ConnectorData:       req.ConnectorData,
		Expiry:              req.Expiry,
		Claims:              toStorageClaims(req.Claims),
		PKCE: storage.PKCE{
			CodeChallenge:       req.CodeChallenge,
			CodeChallengeMethod: req.CodeChallengeMethod,
		},
	}
	return a
}

func (cli *client) fromStorageAuthRequest(a storage.AuthRequest) AuthRequest {
	req := AuthRequest{
		TypeMeta: k8sapi.TypeMeta{
			Kind:       kindAuthRequest,
			APIVersion: cli.apiVersion,
		},
		ObjectMeta: k8sapi.ObjectMeta{
			Name:      a.ID,
			Namespace: cli.namespace,
		},
		ClientID:            a.ClientID,
		ResponseTypes:       a.ResponseTypes,
		Scopes:              a.Scopes,
		RedirectURI:         a.RedirectURI,
		Nonce:               a.Nonce,
		State:               a.State,
		LoggedIn:            a.LoggedIn,
		ForceApprovalPrompt: a.ForceApprovalPrompt,
		ConnectorID:         a.ConnectorID,
		ConnectorData:       a.ConnectorData,
		Expiry:              a.Expiry,
		Claims:              fromStorageClaims(a.Claims),
		CodeChallenge:       a.PKCE.CodeChallenge,
		CodeChallengeMethod: a.PKCE.CodeChallengeMethod,
	}
	return req
}

// Password is a mirrored struct from the storage with JSON struct tags and
// Kubernetes type metadata.
type Password struct {
	k8sapi.TypeMeta   `json:",inline"`
	k8sapi.ObjectMeta `json:"metadata,omitempty"`

	// The Kubernetes name is actually an encoded version of this value.
	//
	// This field is IMMUTABLE. Do not change.
	Email string `json:"email,omitempty"`

	Hash     []byte `json:"hash,omitempty"`
	Username string `json:"username,omitempty"`
	UserID   string `json:"userID,omitempty"`
}

// PasswordList is a list of Passwords.
type PasswordList struct {
	k8sapi.TypeMeta `json:",inline"`
	k8sapi.ListMeta `json:"metadata,omitempty"`
	Passwords       []Password `json:"items"`
}

func (cli *client) fromStoragePassword(p storage.Password) Password {
	email := strings.ToLower(p.Email)
	return Password{
		TypeMeta: k8sapi.TypeMeta{
			Kind:       kindPassword,
			APIVersion: cli.apiVersion,
		},
		ObjectMeta: k8sapi.ObjectMeta{
			Name:      cli.idToName(email),
			Namespace: cli.namespace,
		},
		Email:    email,
		Hash:     p.Hash,
		Username: p.Username,
		UserID:   p.UserID,
	}
}

func toStoragePassword(p Password) storage.Password {
	return storage.Password{
		Email:    p.Email,
		Hash:     p.Hash,
		Username: p.Username,
		UserID:   p.UserID,
	}
}

// AuthCode is a mirrored struct from storage with JSON struct tags and
// Kubernetes type metadata.
type AuthCode struct {
	k8sapi.TypeMeta   `json:",inline"`
	k8sapi.ObjectMeta `json:"metadata,omitempty"`

	ClientID    string   `json:"clientID"`
	Scopes      []string `json:"scopes,omitempty"`
	RedirectURI string   `json:"redirectURI"`

	Nonce string `json:"nonce,omitempty"`
	State string `json:"state,omitempty"`

	Claims Claims `json:"claims,omitempty"`

	ConnectorID   string `json:"connectorID,omitempty"`
	ConnectorData []byte `json:"connectorData,omitempty"`

	Expiry time.Time `json:"expiry"`

	CodeChallenge       string `json:"code_challenge,omitempty"`
	CodeChallengeMethod string `json:"code_challenge_method,omitempty"`
}

// AuthCodeList is a list of AuthCodes.
type AuthCodeList struct {
	k8sapi.TypeMeta `json:",inline"`
	k8sapi.ListMeta `json:"metadata,omitempty"`
	AuthCodes       []AuthCode `json:"items"`
}

func (cli *client) fromStorageAuthCode(a storage.AuthCode) AuthCode {
	return AuthCode{
		TypeMeta: k8sapi.TypeMeta{
			Kind:       kindAuthCode,
			APIVersion: cli.apiVersion,
		},
		ObjectMeta: k8sapi.ObjectMeta{
			Name:      a.ID,
			Namespace: cli.namespace,
		},
		ClientID:            a.ClientID,
		RedirectURI:         a.RedirectURI,
		ConnectorID:         a.ConnectorID,
		ConnectorData:       a.ConnectorData,
		Nonce:               a.Nonce,
		Scopes:              a.Scopes,
		Claims:              fromStorageClaims(a.Claims),
		Expiry:              a.Expiry,
		CodeChallenge:       a.PKCE.CodeChallenge,
		CodeChallengeMethod: a.PKCE.CodeChallengeMethod,
	}
}

func toStorageAuthCode(a AuthCode) storage.AuthCode {
	return storage.AuthCode{
		ID:            a.ObjectMeta.Name,
		ClientID:      a.ClientID,
		RedirectURI:   a.RedirectURI,
		ConnectorID:   a.ConnectorID,
		ConnectorData: a.ConnectorData,
		Nonce:         a.Nonce,
		Scopes:        a.Scopes,
		Claims:        toStorageClaims(a.Claims),
		Expiry:        a.Expiry,
		PKCE: storage.PKCE{
			CodeChallenge:       a.CodeChallenge,
			CodeChallengeMethod: a.CodeChallengeMethod,
		},
	}
}

// RefreshToken is a mirrored struct from storage with JSON struct tags and
// Kubernetes type metadata.
type RefreshToken struct {
	k8sapi.TypeMeta   `json:",inline"`
	k8sapi.ObjectMeta `json:"metadata,omitempty"`

	CreatedAt time.Time
	LastUsed  time.Time

	ClientID string   `json:"clientID"`
	Scopes   []string `json:"scopes,omitempty"`

	Token string `json:"token,omitempty"`

	Nonce string `json:"nonce,omitempty"`

	Claims        Claims `json:"claims,omitempty"`
	ConnectorID   string `json:"connectorID,omitempty"`
	ConnectorData []byte `json:"connectorData,omitempty"`
}

// RefreshList is a list of refresh tokens.
type RefreshList struct {
	k8sapi.TypeMeta `json:",inline"`
	k8sapi.ListMeta `json:"metadata,omitempty"`
	RefreshTokens   []RefreshToken `json:"items"`
}

func toStorageRefreshToken(r RefreshToken) storage.RefreshToken {
	return storage.RefreshToken{
		ID:            r.ObjectMeta.Name,
		Token:         r.Token,
		CreatedAt:     r.CreatedAt,
		LastUsed:      r.LastUsed,
		ClientID:      r.ClientID,
		ConnectorID:   r.ConnectorID,
		ConnectorData: r.ConnectorData,
		Scopes:        r.Scopes,
		Nonce:         r.Nonce,
		Claims:        toStorageClaims(r.Claims),
	}
}

func (cli *client) fromStorageRefreshToken(r storage.RefreshToken) RefreshToken {
	return RefreshToken{
		TypeMeta: k8sapi.TypeMeta{
			Kind:       kindRefreshToken,
			APIVersion: cli.apiVersion,
		},
		ObjectMeta: k8sapi.ObjectMeta{
			Name:      r.ID,
			Namespace: cli.namespace,
		},
		Token:         r.Token,
		CreatedAt:     r.CreatedAt,
		LastUsed:      r.LastUsed,
		ClientID:      r.ClientID,
		ConnectorID:   r.ConnectorID,
		ConnectorData: r.ConnectorData,
		Scopes:        r.Scopes,
		Nonce:         r.Nonce,
		Claims:        fromStorageClaims(r.Claims),
	}
}

// Keys is a mirrored struct from storage with JSON struct tags and Kubernetes
// type metadata.
type Keys struct {
	k8sapi.TypeMeta   `json:",inline"`
	k8sapi.ObjectMeta `json:"metadata,omitempty"`

	// Key for creating and verifying signatures. These may be nil.
	SigningKey    *jose.JSONWebKey `json:"signingKey,omitempty"`
	SigningKeyPub *jose.JSONWebKey `json:"signingKeyPub,omitempty"`
	// Old signing keys which have been rotated but can still be used to validate
	// existing signatures.
	VerificationKeys []storage.VerificationKey `json:"verificationKeys,omitempty"`

	// The next time the signing key will rotate.
	//
	// For caching purposes, implementations MUST NOT update keys before this time.
	NextRotation time.Time `json:"nextRotation"`
}

func (cli *client) fromStorageKeys(keys storage.Keys) Keys {
	return Keys{
		TypeMeta: k8sapi.TypeMeta{
			Kind:       kindKeys,
			APIVersion: cli.apiVersion,
		},
		ObjectMeta: k8sapi.ObjectMeta{
			Name:      keysName,
			Namespace: cli.namespace,
		},
		SigningKey:       keys.SigningKey,
		SigningKeyPub:    keys.SigningKeyPub,
		VerificationKeys: keys.VerificationKeys,
		NextRotation:     keys.NextRotation,
	}
}

func toStorageKeys(keys Keys) storage.Keys {
	return storage.Keys{
		SigningKey:       keys.SigningKey,
		SigningKeyPub:    keys.SigningKeyPub,
		VerificationKeys: keys.VerificationKeys,
		NextRotation:     keys.NextRotation,
	}
}

// OfflineSessions is a mirrored struct from storage with JSON struct tags and Kubernetes
// type metadata.
type OfflineSessions struct {
	k8sapi.TypeMeta   `json:",inline"`
	k8sapi.ObjectMeta `json:"metadata,omitempty"`

	UserID        string                              `json:"userID,omitempty"`
	ConnID        string                              `json:"connID,omitempty"`
	Refresh       map[string]*storage.RefreshTokenRef `json:"refresh,omitempty"`
	ConnectorData []byte                              `json:"connectorData,omitempty"`
}

func (cli *client) fromStorageOfflineSessions(o storage.OfflineSessions) OfflineSessions {
	return OfflineSessions{
		TypeMeta: k8sapi.TypeMeta{
			Kind:       kindOfflineSessions,
			APIVersion: cli.apiVersion,
		},
		ObjectMeta: k8sapi.ObjectMeta{
			Name:      cli.offlineTokenName(o.UserID, o.ConnID),
			Namespace: cli.namespace,
		},
		UserID:        o.UserID,
		ConnID:        o.ConnID,
		Refresh:       o.Refresh,
		ConnectorData: o.ConnectorData,
	}
}

func toStorageOfflineSessions(o OfflineSessions) storage.OfflineSessions {
	s := storage.OfflineSessions{
		UserID:        o.UserID,
		ConnID:        o.ConnID,
		Refresh:       o.Refresh,
		ConnectorData: o.ConnectorData,
	}
	if s.Refresh == nil {
		// Server code assumes this will be non-nil.
		s.Refresh = make(map[string]*storage.RefreshTokenRef)
	}
	return s
}

// Connector is a mirrored struct from storage with JSON struct tags and Kubernetes
// type metadata.
type Connector struct {
	k8sapi.TypeMeta   `json:",inline"`
	k8sapi.ObjectMeta `json:"metadata,omitempty"`

	ID   string `json:"id,omitempty"`
	Type string `json:"type,omitempty"`
	Name string `json:"name,omitempty"`
	// Config holds connector specific configuration information
	Config []byte `json:"config,omitempty"`
}

func (cli *client) fromStorageConnector(c storage.Connector) Connector {
	return Connector{
		TypeMeta: k8sapi.TypeMeta{
			Kind:       kindConnector,
			APIVersion: cli.apiVersion,
		},
		ObjectMeta: k8sapi.ObjectMeta{
			Name:      c.ID,
			Namespace: cli.namespace,
		},
		ID:     c.ID,
		Type:   c.Type,
		Name:   c.Name,
		Config: c.Config,
	}
}

func toStorageConnector(c Connector) storage.Connector {
	return storage.Connector{
		ID:              c.ID,
		Type:            c.Type,
		Name:            c.Name,
		ResourceVersion: c.ObjectMeta.ResourceVersion,
		Config:          c.Config,
	}
}

// ConnectorList is a list of Connectors.
type ConnectorList struct {
	k8sapi.TypeMeta `json:",inline"`
	k8sapi.ListMeta `json:"metadata,omitempty"`
	Connectors      []Connector `json:"items"`
}

// DeviceRequest is a mirrored struct from storage with JSON struct tags and
// Kubernetes type metadata.
type DeviceRequest struct {
	k8sapi.TypeMeta   `json:",inline"`
	k8sapi.ObjectMeta `json:"metadata,omitempty"`

	DeviceCode   string    `json:"device_code,omitempty"`
	ClientID     string    `json:"client_id,omitempty"`
	ClientSecret string    `json:"client_secret,omitempty"`
	Scopes       []string  `json:"scopes,omitempty"`
	Expiry       time.Time `json:"expiry"`
}

// DeviceRequestList is a list of DeviceRequests.
type DeviceRequestList struct {
	k8sapi.TypeMeta `json:",inline"`
	k8sapi.ListMeta `json:"metadata,omitempty"`
	DeviceRequests  []DeviceRequest `json:"items"`
}

func (cli *client) fromStorageDeviceRequest(a storage.DeviceRequest) DeviceRequest {
	req := DeviceRequest{
		TypeMeta: k8sapi.TypeMeta{
			Kind:       kindDeviceRequest,
			APIVersion: cli.apiVersion,
		},
		ObjectMeta: k8sapi.ObjectMeta{
			Name:      strings.ToLower(a.UserCode),
			Namespace: cli.namespace,
		},
		DeviceCode:   a.DeviceCode,
		ClientID:     a.ClientID,
		ClientSecret: a.ClientSecret,
		Scopes:       a.Scopes,
		Expiry:       a.Expiry,
	}
	return req
}

func toStorageDeviceRequest(req DeviceRequest) storage.DeviceRequest {
	return storage.DeviceRequest{
		UserCode:     strings.ToUpper(req.ObjectMeta.Name),
		DeviceCode:   req.DeviceCode,
		ClientID:     req.ClientID,
		ClientSecret: req.ClientSecret,
		Scopes:       req.Scopes,
		Expiry:       req.Expiry,
	}
}

// DeviceToken is a mirrored struct from storage with JSON struct tags and
// Kubernetes type metadata.
type DeviceToken struct {
	k8sapi.TypeMeta   `json:",inline"`
	k8sapi.ObjectMeta `json:"metadata,omitempty"`

	Status              string    `json:"status,omitempty"`
	Token               string    `json:"token,omitempty"`
	Expiry              time.Time `json:"expiry"`
	LastRequestTime     time.Time `json:"last_request"`
	PollIntervalSeconds int       `json:"poll_interval"`
}

// DeviceTokenList is a list of DeviceTokens.
type DeviceTokenList struct {
	k8sapi.TypeMeta `json:",inline"`
	k8sapi.ListMeta `json:"metadata,omitempty"`
	DeviceTokens    []DeviceToken `json:"items"`
}

func (cli *client) fromStorageDeviceToken(t storage.DeviceToken) DeviceToken {
	req := DeviceToken{
		TypeMeta: k8sapi.TypeMeta{
			Kind:       kindDeviceToken,
			APIVersion: cli.apiVersion,
		},
		ObjectMeta: k8sapi.ObjectMeta{
			Name:      t.DeviceCode,
			Namespace: cli.namespace,
		},
		Status:              t.Status,
		Token:               t.Token,
		Expiry:              t.Expiry,
		LastRequestTime:     t.LastRequestTime,
		PollIntervalSeconds: t.PollIntervalSeconds,
	}
	return req
}

func toStorageDeviceToken(t DeviceToken) storage.DeviceToken {
	return storage.DeviceToken{
		DeviceCode:          t.ObjectMeta.Name,
		Status:              t.Status,
		Token:               t.Token,
		Expiry:              t.Expiry,
		LastRequestTime:     t.LastRequestTime,
		PollIntervalSeconds: t.PollIntervalSeconds,
	}
}
