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

package api

import (
	"fmt"
	"sort"
)

// Quotas is used to query the quotas endpoints.
type Quotas struct {
	client *Client
}

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

// List is used to dump all of the quota specs
func (q *Quotas) List(qo *QueryOptions) ([]*QuotaSpec, *QueryMeta, error) {
	var resp []*QuotaSpec
	qm, err := q.client.query("/v1/quotas", &resp, qo)
	if err != nil {
		return nil, nil, err
	}
	sort.Sort(QuotaSpecIndexSort(resp))
	return resp, qm, nil
}

// PrefixList is used to do a PrefixList search over quota specs
func (q *Quotas) PrefixList(prefix string, qo *QueryOptions) ([]*QuotaSpec, *QueryMeta, error) {
	if qo == nil {
		qo = &QueryOptions{Prefix: prefix}
	} else {
		qo.Prefix = prefix
	}

	return q.List(qo)
}

// ListUsage is used to dump all of the quota usages
func (q *Quotas) ListUsage(qo *QueryOptions) ([]*QuotaUsage, *QueryMeta, error) {
	var resp []*QuotaUsage
	qm, err := q.client.query("/v1/quota-usages", &resp, qo)
	if err != nil {
		return nil, nil, err
	}
	sort.Sort(QuotaUsageIndexSort(resp))
	return resp, qm, nil
}

// PrefixListUsage is used to do a PrefixList search over quota usages
func (q *Quotas) PrefixListUsage(prefix string, qo *QueryOptions) ([]*QuotaUsage, *QueryMeta, error) {
	if qo == nil {
		qo = &QueryOptions{Prefix: prefix}
	} else {
		qo.Prefix = prefix
	}

	return q.ListUsage(qo)
}

// Info is used to query a single quota spec by its name.
func (q *Quotas) Info(name string, qo *QueryOptions) (*QuotaSpec, *QueryMeta, error) {
	var resp QuotaSpec
	qm, err := q.client.query("/v1/quota/"+name, &resp, qo)
	if err != nil {
		return nil, nil, err
	}
	return &resp, qm, nil
}

// Usage is used to query a single quota usage by its name.
func (q *Quotas) Usage(name string, qo *QueryOptions) (*QuotaUsage, *QueryMeta, error) {
	var resp QuotaUsage
	qm, err := q.client.query("/v1/quota/usage/"+name, &resp, qo)
	if err != nil {
		return nil, nil, err
	}
	return &resp, qm, nil
}

// Register is used to register a quota spec.
func (q *Quotas) Register(spec *QuotaSpec, qo *WriteOptions) (*WriteMeta, error) {
	wm, err := q.client.put("/v1/quota", spec, nil, qo)
	if err != nil {
		return nil, err
	}
	return wm, nil
}

// Delete is used to delete a quota spec
func (q *Quotas) Delete(quota string, qo *WriteOptions) (*WriteMeta, error) {
	wm, err := q.client.delete(fmt.Sprintf("/v1/quota/%s", quota), nil, nil, qo)
	if err != nil {
		return nil, err
	}
	return wm, nil
}

// QuotaSpec specifies the allowed resource usage across regions.
type QuotaSpec struct {
	// Name is the name for the quota object
	Name string

	// Description is an optional description for the quota object
	Description string

	// Limits is the set of quota limits encapsulated by this quota object. Each
	// limit applies quota in a particular region and in the future over a
	// particular priority range and datacenter set.
	Limits []*QuotaLimit

	// Raft indexes to track creation and modification
	CreateIndex uint64
	ModifyIndex uint64
}

// QuotaLimit describes the resource limit in a particular region.
type QuotaLimit struct {
	// Region is the region in which this limit has affect
	Region string

	// RegionLimit is the quota limit that applies to any allocation within a
	// referencing namespace in the region. A value of zero is treated as
	// unlimited and a negative value is treated as fully disallowed. This is
	// useful for once we support GPUs
	RegionLimit *QuotaResources

	// VariablesLimit is the maximum total size of all variables
	// Variable.EncryptedData. A value of zero is treated as unlimited and a
	// negative value is treated as fully disallowed.
	//
	// DEPRECATED: use RegionLimit.Storage.VariablesMB instead. This field will
	// be removed in Nomad 1.12.0.
	VariablesLimit *int `mapstructure:"variables_limit" hcl:"variables_limit,optional"`

	// Hash is the hash of the object and is used to make replication efficient.
	Hash []byte
}

type QuotaResources struct {
	CPU         *int                   `hcl:"cpu,optional"`
	Cores       *int                   `hcl:"cores,optional"`
	MemoryMB    *int                   `mapstructure:"memory" hcl:"memory,optional"`
	MemoryMaxMB *int                   `mapstructure:"memory_max" hcl:"memory_max,optional"`
	Devices     []*RequestedDevice     `hcl:"device,block"`
	NUMA        *NUMAResource          `hcl:"numa,block"`
	SecretsMB   *int                   `mapstructure:"secrets" hcl:"secrets,optional"`
	Storage     *QuotaStorageResources `mapstructure:"storage" hcl:"storage,block"`
}

type QuotaStorageResources struct {
	// VariablesMB is the maximum total size of all variables
	// Variable.EncryptedData, in megabytes (2^20 bytes). A value of zero is
	// treated as unlimited and a negative value is treated as fully disallowed.
	VariablesMB int `hcl:"variables"`

	// HostVolumesMB is the maximum provisioned size of all dynamic host
	// volumes, in megabytes (2^20 bytes). A value of zero is treated as
	// unlimited and a negative value is treated as fully disallowed.
	HostVolumesMB int `hcl:"host_volumes"`
}

// QuotaUsage is the resource usage of a Quota
type QuotaUsage struct {
	Name        string
	Used        map[string]*QuotaLimit
	CreateIndex uint64
	ModifyIndex uint64
}

// QuotaSpecIndexSort is a wrapper to sort QuotaSpecs by CreateIndex. We
// reverse the test so that we get the highest index first.
type QuotaSpecIndexSort []*QuotaSpec

func (q QuotaSpecIndexSort) Len() int {
	return len(q)
}

func (q QuotaSpecIndexSort) Less(i, j int) bool {
	return q[i].CreateIndex > q[j].CreateIndex
}

func (q QuotaSpecIndexSort) Swap(i, j int) {
	q[i], q[j] = q[j], q[i]
}

// QuotaUsageIndexSort is a wrapper to sort QuotaUsages by CreateIndex. We
// reverse the test so that we get the highest index first.
type QuotaUsageIndexSort []*QuotaUsage

func (q QuotaUsageIndexSort) Len() int {
	return len(q)
}

func (q QuotaUsageIndexSort) Less(i, j int) bool {
	return q[i].CreateIndex > q[j].CreateIndex
}

func (q QuotaUsageIndexSort) Swap(i, j int) {
	q[i], q[j] = q[j], q[i]
}

// QuotaLimitSort is a wrapper to sort QuotaLimits
type QuotaLimitSort []*QuotaLimit

func (q QuotaLimitSort) Len() int {
	return len(q)
}

func (q QuotaLimitSort) Less(i, j int) bool {
	return q[i].Region < q[j].Region
}

func (q QuotaLimitSort) Swap(i, j int) {
	q[i], q[j] = q[j], q[i]
}
