package transport

import (
	"net/http"

	"github.com/go-chi/chi"
	"github.com/go-chi/chi/middleware"
	"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"
)

const (
	prefixNotebooks     = "/api/v2private/notebooks"
	allNotebooksJSONKey = "flows"
)

var (
	errBadOrg = &errors.Error{
		Code: errors.EInvalid,
		Msg:  "invalid or missing org id",
	}

	errBadId = &errors.Error{
		Code: errors.EInvalid,
		Msg:  "notebook id is invalid",
	}
)

// NotebookHandler is the handler for the notebook service
type NotebookHandler struct {
	chi.Router

	log *zap.Logger
	api *kithttp.API

	notebookService influxdb.NotebookService
}

func NewNotebookHandler(
	log *zap.Logger,
	notebookService influxdb.NotebookService,
) *NotebookHandler {
	h := &NotebookHandler{
		log:             log,
		api:             kithttp.NewAPI(kithttp.WithLog(log)),
		notebookService: notebookService,
	}

	r := chi.NewRouter()
	r.Use(
		middleware.Recoverer,
		middleware.RequestID,
		middleware.RealIP,
	)

	r.Route("/", func(r chi.Router) {
		r.Get("/", h.handleGetNotebooks)
		r.Post("/", h.handleCreateNotebook)

		r.Route("/{id}", func(r chi.Router) {
			r.Get("/", h.handleGetNotebook)
			r.Delete("/", h.handleDeleteNotebook)
			r.Put("/", h.handleUpdateNotebook)
			r.Patch("/", h.handleUpdateNotebook)
		})
	})

	h.Router = r

	return h
}

func (h *NotebookHandler) Prefix() string {
	return prefixNotebooks
}

// get a list of all notebooks for an org
func (h *NotebookHandler) handleGetNotebooks(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	orgID := r.URL.Query().Get("orgID")
	o, err := platform.IDFromString(orgID)
	if err != nil {
		h.api.Err(w, r, errBadOrg)
		return
	}

	l, err := h.notebookService.ListNotebooks(ctx, influxdb.NotebookListFilter{OrgID: *o})
	if err != nil {
		h.api.Err(w, r, err)
		return
	}

	p := map[string][]*influxdb.Notebook{
		allNotebooksJSONKey: l,
	}

	h.api.Respond(w, r, http.StatusOK, p)
}

// create a single notebook.
func (h *NotebookHandler) handleCreateNotebook(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	b, err := h.decodeNotebookReqBody(r)
	if err != nil {
		h.api.Err(w, r, err)
		return
	}

	n, err := h.notebookService.CreateNotebook(ctx, b)
	if err != nil {
		h.api.Err(w, r, err)
		return
	}

	h.api.Respond(w, r, http.StatusOK, n)
}

// get a single notebook.
func (h *NotebookHandler) handleGetNotebook(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	id, err := platform.IDFromString(chi.URLParam(r, "id"))
	if err != nil {
		h.api.Err(w, r, errBadId)
		return
	}

	b, err := h.notebookService.GetNotebook(ctx, *id)
	if err != nil {
		h.api.Err(w, r, err)
		return
	}

	h.api.Respond(w, r, http.StatusOK, b)
}

// delete a single notebook.
func (h *NotebookHandler) handleDeleteNotebook(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	id, err := platform.IDFromString(chi.URLParam(r, "id"))
	if err != nil {
		h.api.Err(w, r, errBadId)
		return
	}

	if err := h.notebookService.DeleteNotebook(ctx, *id); err != nil {
		h.api.Err(w, r, err)
		return
	}

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

// update a single notebook.
func (h *NotebookHandler) handleUpdateNotebook(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	id, err := platform.IDFromString(chi.URLParam(r, "id"))
	if err != nil {
		h.api.Err(w, r, errBadId)
		return
	}

	b, err := h.decodeNotebookReqBody(r)
	if err != nil {
		h.api.Err(w, r, err)
		return
	}

	u, err := h.notebookService.UpdateNotebook(ctx, *id, b)
	if err != nil {
		h.api.Err(w, r, err)
		return
	}

	h.api.Respond(w, r, http.StatusOK, u)
}

func (h *NotebookHandler) decodeNotebookReqBody(r *http.Request) (*influxdb.NotebookReqBody, error) {
	b := &influxdb.NotebookReqBody{}
	if err := h.api.DecodeJSON(r.Body, b); err != nil {
		return nil, err
	}

	if err := b.Validate(); err != nil {
		return nil, err
	}

	return b, nil
}