package secret

import (
	"fmt"
	"net/http"

	"github.com/go-chi/chi"
	"github.com/influxdata/influxdb/v2"
	"github.com/influxdata/influxdb/v2/kit/platform"
	"github.com/influxdata/influxdb/v2/kit/platform/errors"
	kithttp "github.com/influxdata/influxdb/v2/kit/transport/http"
	"go.uber.org/zap"
)

type handler struct {
	log *zap.Logger
	svc influxdb.SecretService
	api *kithttp.API

	idLookupKey string
}

// NewHandler creates a new handler for the secret service
func NewHandler(log *zap.Logger, idLookupKey string, svc influxdb.SecretService) http.Handler {
	h := &handler{
		log: log,
		svc: svc,
		api: kithttp.NewAPI(kithttp.WithLog(log)),

		idLookupKey: idLookupKey,
	}

	r := chi.NewRouter()

	r.Get("/", h.handleGetSecrets)
	r.Patch("/", h.handlePatchSecrets)
	r.Delete("/{secretID}", h.handleDeleteSecret)
	r.Post("/delete", h.handleDeleteSecrets) // deprecated
	return r
}

// handleGetSecrets is the HTTP handler for the GET /api/v2/orgs/:id/secrets route.
func (h *handler) handleGetSecrets(w http.ResponseWriter, r *http.Request) {
	orgID, err := h.decodeOrgID(r)
	if err != nil {
		h.api.Err(w, r, err)
	}

	ks, err := h.svc.GetSecretKeys(r.Context(), orgID)
	if err != nil && errors.ErrorCode(err) != errors.ENotFound {
		h.api.Err(w, r, err)
		return
	}

	h.api.Respond(w, r, http.StatusOK, newSecretsResponse(orgID, ks))
}

type secretsResponse struct {
	Links   map[string]string `json:"links"`
	Secrets []string          `json:"secrets"`
}

func newSecretsResponse(orgID platform.ID, ks []string) *secretsResponse {
	if ks == nil {
		ks = []string{}
	}
	return &secretsResponse{
		Links: map[string]string{
			"org":  fmt.Sprintf("/api/v2/orgs/%s", orgID),
			"self": fmt.Sprintf("/api/v2/orgs/%s/secrets", orgID),
		},
		Secrets: ks,
	}
}

// handleGetPatchSecrets is the HTTP handler for the PATCH /api/v2/orgs/:id/secrets route.
func (h *handler) handlePatchSecrets(w http.ResponseWriter, r *http.Request) {
	orgID, err := h.decodeOrgID(r)
	if err != nil {
		h.api.Err(w, r, err)
	}

	var secrets map[string]string
	if err := h.api.DecodeJSON(r.Body, &secrets); err != nil {
		h.api.Err(w, r, err)
		return
	}

	if err := h.svc.PatchSecrets(r.Context(), orgID, secrets); err != nil {
		h.api.Err(w, r, err)
		return
	}

	h.api.Respond(w, r, http.StatusNoContent, nil)
}

type secretsDeleteBody struct {
	Secrets []string `json:"secrets"`
}

// handleDeleteSecrets is the HTTP handler for the POST /api/v2/orgs/:id/secrets/delete route.
// deprecated.
func (h *handler) handleDeleteSecrets(w http.ResponseWriter, r *http.Request) {
	orgID, err := h.decodeOrgID(r)
	if err != nil {
		h.api.Err(w, r, err)
	}

	var reqBody secretsDeleteBody

	if err := h.api.DecodeJSON(r.Body, &reqBody); err != nil {
		h.api.Err(w, r, err)
		return
	}

	if err := h.svc.DeleteSecret(r.Context(), orgID, reqBody.Secrets...); err != nil {
		h.api.Err(w, r, err)
		return
	}

	h.api.Respond(w, r, http.StatusNoContent, nil)
}

// handleDeleteSecret is the HTTP handler for the DELETE /api/v2/orgs/:id/secrets/:id route.
func (h *handler) handleDeleteSecret(w http.ResponseWriter, r *http.Request) {
	orgID, err := h.decodeOrgID(r)
	if err != nil {
		h.api.Err(w, r, err)
	}

	if err := h.svc.DeleteSecret(r.Context(), orgID, chi.URLParam(r, "secretID")); err != nil {
		h.api.Err(w, r, err)
		return
	}

	h.api.Respond(w, r, http.StatusNoContent, nil)
}

func (h *handler) decodeOrgID(r *http.Request) (platform.ID, error) {
	org := chi.URLParam(r, h.idLookupKey)
	if org == "" {
		return platform.InvalidID(), &errors.Error{
			Code: errors.EInvalid,
			Msg:  "url missing id",
		}
	}
	id, err := platform.IDFromString(org)
	if err != nil {
		return platform.InvalidID(), err
	}
	return *id, nil
}
