package handlers

import (
	"fmt"

	"github.com/NdoleStudio/httpsms/pkg/repositories"

	"github.com/NdoleStudio/httpsms/pkg/requests"
	"github.com/google/uuid"

	"github.com/NdoleStudio/httpsms/pkg/services"
	"github.com/NdoleStudio/httpsms/pkg/telemetry"
	"github.com/NdoleStudio/httpsms/pkg/validators"
	"github.com/davecgh/go-spew/spew"
	"github.com/gofiber/fiber/v3"
	"github.com/palantir/stacktrace"
)

// WebhookHandler handles webhook requests
type WebhookHandler struct {
	handler
	logger    telemetry.Logger
	tracer    telemetry.Tracer
	service   *services.WebhookService
	validator *validators.WebhookHandlerValidator
}

// NewWebhookHandler creates a new WebhookHandler
func NewWebhookHandler(
	logger telemetry.Logger,
	tracer telemetry.Tracer,
	service *services.WebhookService,
	validator *validators.WebhookHandlerValidator,
) (h *WebhookHandler) {
	return &WebhookHandler{
		logger:    logger.WithService(fmt.Sprintf("%T", h)),
		tracer:    tracer,
		service:   service,
		validator: validator,
	}
}

// RegisterRoutes registers the routes for the WebhookHandler
func (h *WebhookHandler) RegisterRoutes(router fiber.Router, middlewares ...fiber.Handler) {
	h.register(router, fiber.MethodGet, "/v1/webhooks", middlewares, h.Index)
	h.register(router, fiber.MethodPost, "/v1/webhooks", middlewares, h.Store)
	h.register(router, fiber.MethodPut, "/v1/webhooks/:webhookID", middlewares, h.Update)
	h.register(router, fiber.MethodDelete, "/v1/webhooks/:webhookID", middlewares, h.Delete)
}

// Index returns the webhooks of a user
// @Summary      Get webhooks of a user
// @Description  Get the webhooks of a user
// @Security	 ApiKeyAuth
// @Tags         Webhooks
// @Accept       json
// @Produce      json
// @Param        skip		query  int  	false	"number of webhooks to skip"		minimum(0)
// @Param        query		query  string  	false 	"filter webhooks containing query"
// @Param        limit		query  int  	false	"number of webhooks to return"	minimum(1)	maximum(20)
// @Success      200 		{object}	responses.WebhooksResponse
// @Failure      400		{object}	responses.BadRequest
// @Failure 	 401	    {object}	responses.Unauthorized
// @Failure      422		{object}	responses.UnprocessableEntity
// @Failure      500		{object}	responses.InternalServerError
// @Router       /webhooks 	[get]
func (h *WebhookHandler) Index(c fiber.Ctx) error {
	ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger)
	defer span.End()

	var request requests.WebhookIndex
	if err := c.Bind().Query(&request); err != nil {
		msg := fmt.Sprintf("cannot marshall URL [%s] into %T", c.OriginalURL(), request)
		ctxLogger.Warn(stacktrace.Propagate(err, msg))
		return h.responseBadRequest(c, err)
	}

	if errors := h.validator.ValidateIndex(ctx, request.Sanitize()); len(errors) != 0 {
		msg := fmt.Sprintf("validation errors [%s], while fetching webhooks [%+#v]", spew.Sdump(errors), request)
		ctxLogger.Warn(stacktrace.NewError(msg))
		return h.responseUnprocessableEntity(c, errors, "validation errors while fetching webhooks")
	}

	webhooks, err := h.service.Index(ctx, h.userIDFomContext(c), request.ToIndexParams())
	if err != nil {
		msg := fmt.Sprintf("cannot get webhooks with params [%+#v]", request)
		ctxLogger.Error(stacktrace.Propagate(err, msg))
		return h.responseInternalServerError(c)
	}

	return h.responseOK(c, fmt.Sprintf("fetched %d %s", len(webhooks), h.pluralize("webhook", len(webhooks))), webhooks)
}

// Delete a webhook
// @Summary      Delete webhook
// @Description  Delete a webhook for a user
// @Security	 ApiKeyAuth
// @Tags         Webhooks
// @Accept       json
// @Produce      json
// @Param 		 webhookID 	path		string 							true 	"ID of the webhook"	default(32343a19-da5e-4b1b-a767-3298a73703ca)
// @Success      204		{object}    responses.NoContent
// @Failure      400		{object}	responses.BadRequest
// @Failure 	 401    	{object}	responses.Unauthorized
// @Failure      422		{object}	responses.UnprocessableEntity
// @Failure      500		{object}	responses.InternalServerError
// @Router       /webhooks/{webhookID} [delete]
func (h *WebhookHandler) Delete(c fiber.Ctx) error {
	ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger)
	defer span.End()

	webhookID := c.Params("webhookID")
	if errors := h.validator.ValidateUUID(webhookID, "webhookID"); len(errors) != 0 {
		msg := fmt.Sprintf("validation errors [%s], while deleting webhook with ID [%s]", spew.Sdump(errors), webhookID)
		ctxLogger.Warn(stacktrace.NewError(msg))
		return h.responseUnprocessableEntity(c, errors, "validation errors while deleting webhook")
	}

	err := h.service.Delete(ctx, h.userIDFomContext(c), uuid.MustParse(webhookID))
	if err != nil {
		msg := fmt.Sprintf("cannot delete webhook with ID [%+#v]", webhookID)
		ctxLogger.Error(stacktrace.Propagate(err, msg))
		return h.responseInternalServerError(c)
	}

	return h.responseOK(c, "webhook deleted successfully", nil)
}

// Store a webhook
// @Summary      Store a webhook
// @Description  Store a webhook for the authenticated user
// @Security	 ApiKeyAuth
// @Tags         Webhooks
// @Accept       json
// @Produce      json
// @Param        payload   	body 		requests.WebhookStore  		true "Payload of the webhook request"
// @Success      200 		{object}	responses.WebhookResponse
// @Failure      400		{object}	responses.BadRequest
// @Failure 	 401	    {object}	responses.Unauthorized
// @Failure      422		{object}	responses.UnprocessableEntity
// @Failure      500		{object}	responses.InternalServerError
// @Router       /webhooks [post]
func (h *WebhookHandler) Store(c fiber.Ctx) error {
	ctx, span := h.tracer.StartFromFiberCtx(c)
	defer span.End()

	ctxLogger := h.tracer.CtxLogger(h.logger, span)

	var request requests.WebhookStore
	if err := c.Bind().Body(&request); err != nil {
		msg := fmt.Sprintf("cannot marshall body [%s] into [%T]", c.Body(), request)
		ctxLogger.Warn(stacktrace.Propagate(err, msg))
		return h.responseBadRequest(c, err)
	}

	if errors := h.validator.ValidateStore(ctx, h.userIDFomContext(c), request.Sanitize()); len(errors) != 0 {
		msg := fmt.Sprintf("validation errors [%s], while storing webhook [%+#v]", spew.Sdump(errors), request)
		ctxLogger.Warn(stacktrace.NewError(msg))
		return h.responseUnprocessableEntity(c, errors, "validation errors while storing webhook")
	}

	webhooks, err := h.service.Index(ctx, h.userIDFomContext(c), repositories.IndexParams{Skip: 0, Limit: 10})
	if err != nil {
		ctxLogger.Error(stacktrace.Propagate(err, fmt.Sprintf("cannot index webhooks for user [%s]", h.userIDFomContext(c))))
		return h.responseInternalServerError(c)
	}

	if len(webhooks) == 10 {
		ctxLogger.Warn(stacktrace.NewError(fmt.Sprintf("user with ID [%s] wants to create more than 5 webhooks", h.userIDFomContext(c))))
		return h.responsePaymentRequired(c, "You can't create more than 10 webhooks contact us to upgrade to our enterprise plan.")
	}

	webhook, err := h.service.Store(ctx, request.ToStoreParams(h.userFromContext(c)))
	if err != nil {
		msg := fmt.Sprintf("cannot store webhoook with params [%+#v]", request)
		ctxLogger.Error(stacktrace.Propagate(err, msg))
		return h.responseInternalServerError(c)
	}

	return h.responseCreated(c, "webhook created successfully", webhook)
}

// Update an entities.Webhook
// @Summary      Update a webhook
// @Description  Update a webhook for the currently authenticated user
// @Security	 ApiKeyAuth
// @Tags         Webhooks
// @Accept       json
// @Produce      json
// @Param 		 webhookID	path		string 							true 	"ID of the webhook" 					default(32343a19-da5e-4b1b-a767-3298a73703ca)
// @Param        payload   	body 		requests.WebhookUpdate  		true 	"Payload of webhook details to update"
// @Success      200 		{object}	responses.WebhookResponse
// @Failure      400		{object}	responses.BadRequest
// @Failure 	 401    	{object}	responses.Unauthorized
// @Failure      422		{object}	responses.UnprocessableEntity
// @Failure      500		{object}	responses.InternalServerError
// @Router       /webhooks/{webhookID} 	[put]
func (h *WebhookHandler) Update(c fiber.Ctx) error {
	ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger)
	defer span.End()

	var request requests.WebhookUpdate
	if err := c.Bind().Body(&request); err != nil {
		msg := fmt.Sprintf("cannot marshall params [%s] into [%T]", c.Body(), request)
		ctxLogger.Warn(stacktrace.Propagate(err, msg))
		return h.responseBadRequest(c, err)
	}

	request.WebhookID = c.Params("webhookID")
	if errors := h.validator.ValidateUpdate(ctx, h.userIDFomContext(c), request.Sanitize()); len(errors) != 0 {
		msg := fmt.Sprintf("validation errors [%s], while updating user [%+#v]", spew.Sdump(errors), request)
		ctxLogger.Warn(stacktrace.NewError(msg))
		return h.responseUnprocessableEntity(c, errors, "validation errors while updating webhook")
	}

	user, err := h.service.Update(ctx, request.ToUpdateParams(h.userFromContext(c)))
	if err != nil {
		msg := fmt.Sprintf("cannot update user with params [%+#v]", request)
		ctxLogger.Error(stacktrace.Propagate(err, msg))
		return h.responseInternalServerError(c)
	}

	return h.responseOK(c, "webhook updated successfully", user)
}
