package secrets

import (
	"encoding/json"
	"io"
	"io/ioutil"
	"time"

	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/pagination"
)

// Secret represents a secret stored in the key manager service.
type Secret struct {
	// BitLength is the bit length of the secret.
	BitLength int `json:"bit_length"`

	// Algorithm is the algorithm type of the secret.
	Algorithm string `json:"algorithm"`

	// Expiration is the expiration date of the secret.
	Expiration time.Time `json:"-"`

	// ContentTypes are the content types of the secret.
	ContentTypes map[string]string `json:"content_types"`

	// Created is the created date of the secret.
	Created time.Time `json:"-"`

	// CreatorID is the creator of the secret.
	CreatorID string `json:"creator_id"`

	// Mode is the mode of the secret.
	Mode string `json:"mode"`

	// Name is the name of the secret.
	Name string `json:"name"`

	// SecretRef is the URL to the secret.
	SecretRef string `json:"secret_ref"`

	// SecretType represents the type of secret.
	SecretType string `json:"secret_type"`

	// Status represents the status of the secret.
	Status string `json:"status"`

	// Updated is the updated date of the secret.
	Updated time.Time `json:"-"`
}

func (r *Secret) UnmarshalJSON(b []byte) error {
	type tmp Secret
	var s struct {
		tmp
		Created    gophercloud.JSONRFC3339NoZ `json:"created"`
		Updated    gophercloud.JSONRFC3339NoZ `json:"updated"`
		Expiration gophercloud.JSONRFC3339NoZ `json:"expiration"`
	}
	err := json.Unmarshal(b, &s)
	if err != nil {
		return err
	}
	*r = Secret(s.tmp)

	r.Created = time.Time(s.Created)
	r.Updated = time.Time(s.Updated)
	r.Expiration = time.Time(s.Expiration)

	return nil
}

type commonResult struct {
	gophercloud.Result
}

// Extract interprets any commonResult as a Secret.
func (r commonResult) Extract() (*Secret, error) {
	var s *Secret
	err := r.ExtractInto(&s)
	return s, err
}

// GetResult is the response from a Get operation. Call its Extract method
// to interpret it as a secrets.
type GetResult struct {
	commonResult
}

// CreateResult is the response from a Create operation. Call its Extract method
// to interpret it as a secrets.
type CreateResult struct {
	commonResult
}

// UpdateResult is the response from an Update operation. Call its ExtractErr
// method to determine if the request succeeded or failed.
type UpdateResult struct {
	gophercloud.ErrResult
}

// DeleteResult is the response from a Delete operation. Call its ExtractErr
// method to determine if the request succeeded or failed.
type DeleteResult struct {
	gophercloud.ErrResult
}

// PayloadResult is the response from a GetPayload operation. Call its Extract
// method to extract the payload as a string.
type PayloadResult struct {
	gophercloud.Result
	Body io.ReadCloser
}

// Extract is a method that takes a PayloadResult's io.Reader body and reads
// all available data into a slice of bytes. Please be aware that its io.Reader
// is forward-only - meaning that it can only be read once and not rewound. You
// can recreate a reader from the output of this function by using
// bytes.NewReader(downloadBytes)
func (r PayloadResult) Extract() ([]byte, error) {
	if r.Err != nil {
		return nil, r.Err
	}
	defer r.Body.Close()
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		return nil, err
	}
	return body, nil
}

// SecretPage is a single page of secrets results.
type SecretPage struct {
	pagination.LinkedPageBase
}

// IsEmpty determines whether or not a page of secrets contains any results.
func (r SecretPage) IsEmpty() (bool, error) {
	if r.StatusCode == 204 {
		return true, nil
	}

	secrets, err := ExtractSecrets(r)
	return len(secrets) == 0, err
}

// NextPageURL extracts the "next" link from the links section of the result.
func (r SecretPage) NextPageURL() (string, error) {
	var s struct {
		Next     string `json:"next"`
		Previous string `json:"previous"`
	}
	err := r.ExtractInto(&s)
	if err != nil {
		return "", err
	}
	return s.Next, err
}

// ExtractSecrets returns a slice of Secrets contained in a single page of
// results.
func ExtractSecrets(r pagination.Page) ([]Secret, error) {
	var s struct {
		Secrets []Secret `json:"secrets"`
	}
	err := (r.(SecretPage)).ExtractInto(&s)
	return s.Secrets, err
}

// MetadataResult is the result of a metadata request. Call its Extract method
// to interpret it as a map[string]string.
type MetadataResult struct {
	gophercloud.Result
}

// Extract interprets any MetadataResult as map[string]string.
func (r MetadataResult) Extract() (map[string]string, error) {
	var s struct {
		Metadata map[string]string `json:"metadata"`
	}
	err := r.ExtractInto(&s)
	return s.Metadata, err
}

// MetadataCreateResult is the result of a metadata create request. Call its
// Extract method to interpret it as a map[string]string.
type MetadataCreateResult struct {
	gophercloud.Result
}

// Extract interprets any MetadataCreateResult as a map[string]string.
func (r MetadataCreateResult) Extract() (map[string]string, error) {
	var s map[string]string
	err := r.ExtractInto(&s)
	return s, err
}

// Metadatum represents an individual metadata.
type Metadatum struct {
	Key   string `json:"key"`
	Value string `json:"value"`
}

// MetadatumResult is the result of a metadatum request. Call its
// Extract method to interpret it as a map[string]string.
type MetadatumResult struct {
	gophercloud.Result
}

// Extract interprets any MetadatumResult as a map[string]string.
func (r MetadatumResult) Extract() (*Metadatum, error) {
	var s *Metadatum
	err := r.ExtractInto(&s)
	return s, err
}

// MetadatumCreateResult is the response from a metadata Create operation. Call
// its ExtractErr method to determine if the request succeeded or failed.
//
// NOTE: This could be a MetadatumResponse but, at the time of testing, it looks
// like Barbican was returning errneous JSON in the response.
type MetadatumCreateResult struct {
	gophercloud.ErrResult
}

// MetadatumDeleteResult is the response from a metadatum Delete operation. Call
// its ExtractErr method to determine if the request succeeded or failed.
type MetadatumDeleteResult struct {
	gophercloud.ErrResult
}
