package handlers

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

	"github.com/go-logr/logr"

	"code.cloudfoundry.org/korifi/api/authorization"
	apierrors "code.cloudfoundry.org/korifi/api/errors"
	"code.cloudfoundry.org/korifi/api/payloads"
	"code.cloudfoundry.org/korifi/api/presenter"
	"code.cloudfoundry.org/korifi/api/repositories"
	"code.cloudfoundry.org/korifi/api/routing"
)

const (
	DropletsPath = "/v3/droplets"
	DropletPath  = "/v3/droplets/{guid}"
)

//counterfeiter:generate -o fake -fake-name CFDropletRepository . CFDropletRepository
type CFDropletRepository interface {
	GetDroplet(context.Context, authorization.Info, string) (repositories.DropletRecord, error)
	ListDroplets(context.Context, authorization.Info, repositories.ListDropletsMessage) (repositories.ListResult[repositories.DropletRecord], error)
	UpdateDroplet(context.Context, authorization.Info, repositories.UpdateDropletMessage) (repositories.DropletRecord, error)
}

type Droplet struct {
	serverURL        url.URL
	dropletRepo      CFDropletRepository
	requestValidator RequestValidator
}

func NewDroplet(
	serverURL url.URL,
	dropletRepo CFDropletRepository,
	requestValidator RequestValidator,
) *Droplet {
	return &Droplet{
		serverURL:        serverURL,
		dropletRepo:      dropletRepo,
		requestValidator: requestValidator,
	}
}

func (h *Droplet) get(r *http.Request) (*routing.Response, error) {
	authInfo, _ := authorization.InfoFromContext(r.Context())
	logger := logr.FromContextOrDiscard(r.Context()).WithName("handlers.droplet.get")

	dropletGUID := routing.URLParam(r, "guid")

	droplet, err := h.dropletRepo.GetDroplet(r.Context(), authInfo, dropletGUID)
	if err != nil {
		return nil, apierrors.LogAndReturn(
			logger,
			apierrors.ForbiddenAsNotFound(err),
			fmt.Sprintf("Failed to fetch %s from Kubernetes", repositories.DropletResourceType),
			"guid", dropletGUID,
		)
	}

	return routing.NewResponse(http.StatusOK).WithBody(presenter.ForDroplet(droplet, h.serverURL)), nil
}

func (h *Droplet) list(r *http.Request) (*routing.Response, error) {
	authInfo, _ := authorization.InfoFromContext(r.Context())
	logger := logr.FromContextOrDiscard(r.Context()).WithName("handlers.droplet.list")

	payload := new(payloads.DropletList)
	err := h.requestValidator.DecodeAndValidateURLValues(r, payload)
	if err != nil {
		return nil, apierrors.LogAndReturn(logger, err, "Unable to decode request query parameters")
	}

	listResult, err := h.dropletRepo.ListDroplets(r.Context(), authInfo, payload.ToMessage())
	if err != nil {
		return nil, apierrors.LogAndReturn(logger, err, "Failed to list droplets")
	}

	return routing.NewResponse(http.StatusOK).WithBody(presenter.ForList(presenter.ForDroplet, listResult, h.serverURL, *r.URL)), nil
}

func (h *Droplet) update(r *http.Request) (*routing.Response, error) {
	authInfo, _ := authorization.InfoFromContext(r.Context())
	logger := logr.FromContextOrDiscard(r.Context()).WithName("handlers.droplet.update")

	dropletGUID := routing.URLParam(r, "guid")

	var payload payloads.DropletUpdate
	if err := h.requestValidator.DecodeAndValidateJSONPayload(r, &payload); err != nil {
		return nil, apierrors.LogAndReturn(logger, err, "failed to decode payload")
	}

	_, err := h.dropletRepo.GetDroplet(r.Context(), authInfo, dropletGUID)
	if err != nil {
		return nil, apierrors.LogAndReturn(
			logger,
			apierrors.ForbiddenAsNotFound(err),
			fmt.Sprintf("Failed to fetch %s from Kubernetes", repositories.DropletResourceType),
			"guid", dropletGUID,
		)
	}

	droplet, err := h.dropletRepo.UpdateDroplet(r.Context(), authInfo, payload.ToMessage(dropletGUID))
	if err != nil {
		return nil, apierrors.LogAndReturn(logger, err, "Error updating droplet in repository")
	}

	return routing.NewResponse(http.StatusOK).WithBody(presenter.ForDroplet(droplet, h.serverURL)), nil
}

func (h *Droplet) UnauthenticatedRoutes() []routing.Route {
	return nil
}

func (h *Droplet) AuthenticatedRoutes() []routing.Route {
	return []routing.Route{
		{Method: "GET", Pattern: DropletPath, Handler: h.get},
		{Method: "GET", Pattern: DropletsPath, Handler: h.list},
		{Method: "PATCH", Pattern: DropletPath, Handler: h.update},
	}
}
