package cloudflare

import (
	"context"
	"fmt"
	"net/http"
	"net/url"

	"github.com/goccy/go-json"
)

// CreateWorkersKVNamespaceParams provides parameters for creating and updating storage namespaces.
type CreateWorkersKVNamespaceParams struct {
	Title string `json:"title"`
}

type UpdateWorkersKVNamespaceParams struct {
	NamespaceID string `json:"-"`
	Title       string `json:"title"`
}

// WorkersKVPair is used in an array in the request to the bulk KV api.
type WorkersKVPair struct {
	Key           string      `json:"key"`
	Value         string      `json:"value"`
	Expiration    int         `json:"expiration,omitempty"`
	ExpirationTTL int         `json:"expiration_ttl,omitempty"`
	Metadata      interface{} `json:"metadata,omitempty"`
	Base64        bool        `json:"base64,omitempty"`
}

// WorkersKVNamespaceResponse is the response received when creating storage namespaces.
type WorkersKVNamespaceResponse struct {
	Response
	Result WorkersKVNamespace `json:"result"`
}

// WorkersKVNamespace contains the unique identifier and title of a storage namespace.
type WorkersKVNamespace struct {
	ID    string `json:"id"`
	Title string `json:"title"`
}

// ListWorkersKVNamespacesResponse contains a slice of storage namespaces associated with an
// account, pagination information, and an embedded response struct.
type ListWorkersKVNamespacesResponse struct {
	Response
	Result     []WorkersKVNamespace `json:"result"`
	ResultInfo `json:"result_info"`
}

// StorageKey is a key name used to identify a storage value.
type StorageKey struct {
	Name       string      `json:"name"`
	Expiration int         `json:"expiration"`
	Metadata   interface{} `json:"metadata"`
}

// ListStorageKeysResponse contains a slice of keys belonging to a storage namespace,
// pagination information, and an embedded response struct.
type ListStorageKeysResponse struct {
	Response
	Result     []StorageKey `json:"result"`
	ResultInfo `json:"result_info"`
}

type ListWorkersKVNamespacesParams struct {
	ResultInfo
}

type WriteWorkersKVEntryParams struct {
	NamespaceID string
	Key         string
	Value       []byte
}

type WriteWorkersKVEntriesParams struct {
	NamespaceID string
	KVs         []*WorkersKVPair
}

type GetWorkersKVParams struct {
	NamespaceID string
	Key         string
}

type DeleteWorkersKVEntryParams struct {
	NamespaceID string
	Key         string
}

type DeleteWorkersKVEntriesParams struct {
	NamespaceID string
	Keys        []string
}

type ListWorkersKVsParams struct {
	NamespaceID string `url:"-"`
	Limit       int    `url:"limit,omitempty"`
	Cursor      string `url:"cursor,omitempty"`
	Prefix      string `url:"prefix,omitempty"`
}

// CreateWorkersKVNamespace creates a namespace under the given title.
// A 400 is returned if the account already owns a namespace with this title.
// A namespace must be explicitly deleted to be replaced.
//
// API reference: https://developers.cloudflare.com/api/resources/kv/subresources/namespaces/methods/create/
func (api *API) CreateWorkersKVNamespace(ctx context.Context, rc *ResourceContainer, params CreateWorkersKVNamespaceParams) (WorkersKVNamespaceResponse, error) {
	if rc.Level != AccountRouteLevel {
		return WorkersKVNamespaceResponse{}, ErrRequiredAccountLevelResourceContainer
	}

	if rc.Identifier == "" {
		return WorkersKVNamespaceResponse{}, ErrMissingIdentifier
	}
	uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces", rc.Identifier)
	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, params)
	if err != nil {
		return WorkersKVNamespaceResponse{}, err
	}

	result := WorkersKVNamespaceResponse{}
	if err := json.Unmarshal(res, &result); err != nil {
		return result, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return result, err
}

// ListWorkersKVNamespaces lists storage namespaces.
//
// API reference: https://developers.cloudflare.com/api/resources/kv/subresources/namespaces/methods/list/
func (api *API) ListWorkersKVNamespaces(ctx context.Context, rc *ResourceContainer, params ListWorkersKVNamespacesParams) ([]WorkersKVNamespace, *ResultInfo, error) {
	if rc.Level != AccountRouteLevel {
		return []WorkersKVNamespace{}, &ResultInfo{}, ErrRequiredAccountLevelResourceContainer
	}

	if rc.Identifier == "" {
		return []WorkersKVNamespace{}, &ResultInfo{}, ErrMissingIdentifier
	}

	autoPaginate := true
	if params.PerPage >= 1 || params.Page >= 1 {
		autoPaginate = false
	}
	if params.PerPage < 1 {
		params.PerPage = 50
	}
	if params.Page < 1 {
		params.Page = 1
	}

	var namespaces []WorkersKVNamespace
	var nsResponse ListWorkersKVNamespacesResponse
	for {
		nsResponse = ListWorkersKVNamespacesResponse{}
		uri := buildURI(fmt.Sprintf("/accounts/%s/storage/kv/namespaces", rc.Identifier), params)

		res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
		if err != nil {
			return []WorkersKVNamespace{}, &ResultInfo{}, err
		}

		err = json.Unmarshal(res, &nsResponse)
		if err != nil {
			return []WorkersKVNamespace{}, &ResultInfo{}, fmt.Errorf("failed to unmarshal workers KV namespaces JSON data: %w", err)
		}

		namespaces = append(namespaces, nsResponse.Result...)
		params.ResultInfo = nsResponse.ResultInfo.Next()

		if params.ResultInfo.Done() || !autoPaginate {
			break
		}
	}

	return namespaces, &nsResponse.ResultInfo, nil
}

// DeleteWorkersKVNamespace deletes the namespace corresponding to the given ID.
//
// API reference: https://developers.cloudflare.com/api/resources/kv/subresources/namespaces/methods/delete/
func (api *API) DeleteWorkersKVNamespace(ctx context.Context, rc *ResourceContainer, namespaceID string) (Response, error) {
	uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s", rc.Identifier, namespaceID)
	res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
	if err != nil {
		return Response{}, err
	}

	result := Response{}
	if err := json.Unmarshal(res, &result); err != nil {
		return result, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return result, err
}

// UpdateWorkersKVNamespace modifies a KV namespace based on the ID.
//
// API reference: https://developers.cloudflare.com/api/resources/kv/subresources/namespaces/methods/update/
func (api *API) UpdateWorkersKVNamespace(ctx context.Context, rc *ResourceContainer, params UpdateWorkersKVNamespaceParams) (Response, error) {
	if rc.Level != AccountRouteLevel {
		return Response{}, ErrRequiredAccountLevelResourceContainer
	}

	if rc.Identifier == "" {
		return Response{}, ErrMissingIdentifier
	}

	uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s", rc.Identifier, params.NamespaceID)
	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, params)
	if err != nil {
		return Response{}, err
	}

	result := Response{}
	if err := json.Unmarshal(res, &result); err != nil {
		return result, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return result, err
}

// WriteWorkersKVEntry writes a single KV value based on the key.
//
// API reference: https://developers.cloudflare.com/api/resources/kv/subresources/namespaces/subresources/values/methods/update/
func (api *API) WriteWorkersKVEntry(ctx context.Context, rc *ResourceContainer, params WriteWorkersKVEntryParams) (Response, error) {
	if rc.Level != AccountRouteLevel {
		return Response{}, ErrRequiredAccountLevelResourceContainer
	}

	if rc.Identifier == "" {
		return Response{}, ErrMissingIdentifier
	}

	uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/values/%s", rc.Identifier, params.NamespaceID, url.PathEscape(params.Key))
	res, err := api.makeRequestContextWithHeaders(
		ctx, http.MethodPut, uri, params.Value, http.Header{"Content-Type": []string{"application/octet-stream"}},
	)
	if err != nil {
		return Response{}, err
	}

	result := Response{}
	if err := json.Unmarshal(res, &result); err != nil {
		return result, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return result, err
}

// WriteWorkersKVEntries writes multiple KVs at once.
//
// API reference: https://developers.cloudflare.com/api/resources/kv/subresources/namespaces/methods/bulk_update/
func (api *API) WriteWorkersKVEntries(ctx context.Context, rc *ResourceContainer, params WriteWorkersKVEntriesParams) (Response, error) {
	if rc.Level != AccountRouteLevel {
		return Response{}, ErrRequiredAccountLevelResourceContainer
	}

	if rc.Identifier == "" {
		return Response{}, ErrMissingIdentifier
	}

	uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/bulk", rc.Identifier, params.NamespaceID)
	res, err := api.makeRequestContextWithHeaders(
		ctx, http.MethodPut, uri, params.KVs, http.Header{"Content-Type": []string{"application/json"}},
	)
	if err != nil {
		return Response{}, err
	}

	result := Response{}
	if err := json.Unmarshal(res, &result); err != nil {
		return result, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return result, err
}

// GetWorkersKV returns the value associated with the given key in the
// given namespace.
//
// API reference: https://developers.cloudflare.com/api/resources/kv/subresources/namespaces/subresources/values/methods/get/
func (api API) GetWorkersKV(ctx context.Context, rc *ResourceContainer, params GetWorkersKVParams) ([]byte, error) {
	if rc.Level != AccountRouteLevel {
		return []byte(``), ErrRequiredAccountLevelResourceContainer
	}

	if rc.Identifier == "" {
		return []byte(``), ErrMissingIdentifier
	}
	uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/values/%s", rc.Identifier, params.NamespaceID, url.PathEscape(params.Key))
	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return nil, err
	}
	return res, nil
}

// DeleteWorkersKVEntry deletes a key and value for a provided storage namespace.
//
// API reference: https://developers.cloudflare.com/api/resources/kv/subresources/namespaces/subresources/values/methods/delete/
func (api API) DeleteWorkersKVEntry(ctx context.Context, rc *ResourceContainer, params DeleteWorkersKVEntryParams) (Response, error) {
	if rc.Level != AccountRouteLevel {
		return Response{}, ErrRequiredAccountLevelResourceContainer
	}

	if rc.Identifier == "" {
		return Response{}, ErrMissingIdentifier
	}
	uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/values/%s", rc.Identifier, params.NamespaceID, url.PathEscape(params.Key))
	res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
	if err != nil {
		return Response{}, err
	}

	result := Response{}
	if err := json.Unmarshal(res, &result); err != nil {
		return result, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return result, err
}

// DeleteWorkersKVEntries deletes multiple KVs at once.
//
// API reference: https://developers.cloudflare.com/api/resources/kv/subresources/namespaces/methods/bulk_delete/
func (api *API) DeleteWorkersKVEntries(ctx context.Context, rc *ResourceContainer, params DeleteWorkersKVEntriesParams) (Response, error) {
	if rc.Level != AccountRouteLevel {
		return Response{}, ErrRequiredAccountLevelResourceContainer
	}

	if rc.Identifier == "" {
		return Response{}, ErrMissingIdentifier
	}
	uri := fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/bulk", rc.Identifier, params.NamespaceID)
	res, err := api.makeRequestContextWithHeaders(
		ctx, http.MethodDelete, uri, params.Keys, http.Header{"Content-Type": []string{"application/json"}},
	)
	if err != nil {
		return Response{}, err
	}

	result := Response{}
	if err := json.Unmarshal(res, &result); err != nil {
		return result, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}

	return result, err
}

// ListWorkersKVKeys lists a namespace's keys.
//
// API Reference: https://developers.cloudflare.com/api/resources/kv/subresources/namespaces/subresources/keys/methods/list/
func (api API) ListWorkersKVKeys(ctx context.Context, rc *ResourceContainer, params ListWorkersKVsParams) (ListStorageKeysResponse, error) {
	if rc.Level != AccountRouteLevel {
		return ListStorageKeysResponse{}, ErrRequiredAccountLevelResourceContainer
	}

	if rc.Identifier == "" {
		return ListStorageKeysResponse{}, ErrMissingIdentifier
	}

	uri := buildURI(
		fmt.Sprintf("/accounts/%s/storage/kv/namespaces/%s/keys", rc.Identifier, params.NamespaceID),
		params,
	)
	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return ListStorageKeysResponse{}, err
	}

	result := ListStorageKeysResponse{}
	if err := json.Unmarshal(res, &result); err != nil {
		return result, fmt.Errorf("%s: %w", errUnmarshalError, err)
	}
	return result, err
}
