package tokens

import (
	"time"

	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
)

// Token provides only the most basic information related to an authentication
// token.
type Token struct {
	// ID provides the primary means of identifying a user to the OpenStack API.
	// OpenStack defines this field as an opaque value, so do not depend on its
	// content. It is safe, however, to compare for equality.
	ID string

	// ExpiresAt provides a timestamp in ISO 8601 format, indicating when the
	// authentication token becomes invalid. After this point in time, future
	// API requests made using this  authentication token will respond with
	// errors. Either the caller will need to reauthenticate manually, or more
	// preferably, the caller should exploit automatic re-authentication.
	// See the AuthOptions structure for more details.
	ExpiresAt time.Time

	// Tenant provides information about the tenant to which this token grants
	// access.
	Tenant tenants.Tenant
}

// Role is a role for a user.
type Role struct {
	Name string `json:"name"`
}

// User is an OpenStack user.
type User struct {
	ID       string `json:"id"`
	Name     string `json:"name"`
	UserName string `json:"username"`
	Roles    []Role `json:"roles"`
}

// Endpoint represents a single API endpoint offered by a service.
// It provides the public and internal URLs, if supported, along with a region
// specifier, again if provided.
//
// The significance of the Region field will depend upon your provider.
//
// In addition, the interface offered by the service will have version
// information associated with it through the VersionId, VersionInfo, and
// VersionList fields, if provided or supported.
//
// In all cases, fields which aren't supported by the provider and service
// combined will assume a zero-value ("").
type Endpoint struct {
	TenantID    string `json:"tenantId"`
	PublicURL   string `json:"publicURL"`
	InternalURL string `json:"internalURL"`
	AdminURL    string `json:"adminURL"`
	Region      string `json:"region"`
	VersionID   string `json:"versionId"`
	VersionInfo string `json:"versionInfo"`
	VersionList string `json:"versionList"`
}

// CatalogEntry provides a type-safe interface to an Identity API V2 service
// catalog listing.
//
// Each class of service, such as cloud DNS or block storage services, will have
// a single CatalogEntry representing it.
//
// Note: when looking for the desired service, try, whenever possible, to key
// off the type field. Otherwise, you'll tie the representation of the service
// to a specific provider.
type CatalogEntry struct {
	// Name will contain the provider-specified name for the service.
	Name string `json:"name"`

	// Type will contain a type string if OpenStack defines a type for the
	// service. Otherwise, for provider-specific services, the provider may assign
	// their own type strings.
	Type string `json:"type"`

	// Endpoints will let the caller iterate over all the different endpoints that
	// may exist for the service.
	Endpoints []Endpoint `json:"endpoints"`
}

// ServiceCatalog provides a view into the service catalog from a previous,
// successful authentication.
type ServiceCatalog struct {
	Entries []CatalogEntry
}

// CreateResult is the response from a Create request. Use ExtractToken() to
// interpret it as a Token, or ExtractServiceCatalog() to interpret it as a
// service catalog.
type CreateResult struct {
	gophercloud.Result
}

// GetResult is the deferred response from a Get call, which is the same with a
// Created token. Use ExtractUser() to interpret it as a User.
type GetResult struct {
	CreateResult
}

// ExtractToken returns the just-created Token from a CreateResult.
func (r CreateResult) ExtractToken() (*Token, error) {
	var s struct {
		Access struct {
			Token struct {
				Expires string         `json:"expires"`
				ID      string         `json:"id"`
				Tenant  tenants.Tenant `json:"tenant"`
			} `json:"token"`
		} `json:"access"`
	}

	err := r.ExtractInto(&s)
	if err != nil {
		return nil, err
	}

	expiresTs, err := time.Parse(gophercloud.RFC3339Milli, s.Access.Token.Expires)
	if err != nil {
		return nil, err
	}

	return &Token{
		ID:        s.Access.Token.ID,
		ExpiresAt: expiresTs,
		Tenant:    s.Access.Token.Tenant,
	}, nil
}

// ExtractTokenID implements the gophercloud.AuthResult interface. The returned
// string is the same as the ID field of the Token struct returned from
// ExtractToken().
func (r CreateResult) ExtractTokenID() (string, error) {
	var s struct {
		Access struct {
			Token struct {
				ID string `json:"id"`
			} `json:"token"`
		} `json:"access"`
	}
	err := r.ExtractInto(&s)
	return s.Access.Token.ID, err
}

// ExtractServiceCatalog returns the ServiceCatalog that was generated along
// with the user's Token.
func (r CreateResult) ExtractServiceCatalog() (*ServiceCatalog, error) {
	var s struct {
		Access struct {
			Entries []CatalogEntry `json:"serviceCatalog"`
		} `json:"access"`
	}
	err := r.ExtractInto(&s)
	return &ServiceCatalog{Entries: s.Access.Entries}, err
}

// ExtractUser returns the User from a GetResult.
func (r GetResult) ExtractUser() (*User, error) {
	var s struct {
		Access struct {
			User User `json:"user"`
		} `json:"access"`
	}
	err := r.ExtractInto(&s)
	return &s.Access.User, err
}
