package billing

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"strconv"

	"github.com/porter-dev/porter/api/server/authz"
	"github.com/porter-dev/porter/api/server/handlers"
	"github.com/porter-dev/porter/api/server/shared"
	"github.com/porter-dev/porter/api/server/shared/apierrors"
	"github.com/porter-dev/porter/api/server/shared/config"
	"gorm.io/gorm"
)

type BillingWebhookHandler struct {
	handlers.PorterHandlerReadWriter
	authz.KubernetesAgentGetter
}

func NewBillingWebhookHandler(
	config *config.Config,
	decoderValidator shared.RequestDecoderValidator,
) http.Handler {
	return &BillingWebhookHandler{
		PorterHandlerReadWriter: handlers.NewDefaultPorterHandler(config, decoderValidator, nil),
	}
}

func (c *BillingWebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	payload, err := ioutil.ReadAll(r.Body)
	if err != nil {
		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
		return
	}

	// verify webhook secret
	signature := r.Header.Get("x-signature")

	if !c.Config().BillingManager.VerifySignature(signature, payload) {
		c.HandleAPIError(w, r, apierrors.NewErrForbidden(
			fmt.Errorf("could not verify signature for billing webhook"),
		))

		return
	}

	// parse usage and update project
	newUsage, features, err := c.Config().BillingManager.ParseProjectUsageFromWebhook(payload)
	if err != nil {
		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
		return
	}

	// newUsage will be nil if webhook event type is not "subscription", so return without
	// updating usage in this case
	if newUsage == nil {
		return
	}

	// update the project's usage
	existingUsage, err := c.Repo().ProjectUsage().ReadProjectUsage(newUsage.ProjectID)
	notFound := errors.Is(err, gorm.ErrRecordNotFound)

	if !notFound && err != nil {
		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
		return
	}

	if notFound {
		_, err = c.Repo().ProjectUsage().CreateProjectUsage(newUsage)
	} else {
		newUsage.ID = existingUsage.ID
		_, err = c.Repo().ProjectUsage().UpdateProjectUsage(newUsage)
	}

	if err != nil {
		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
		return
	}

	// update the feature flags
	project, err := c.Repo().Project().ReadProject(newUsage.ProjectID)
	if err != nil {
		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
		return
	}

	if managedDatabasesEnabled, err := strconv.ParseBool(features.ManagedDatabasesEnabled); err == nil {
		project.RDSDatabasesEnabled = managedDatabasesEnabled
	}

	if managedInfraEnabled, err := strconv.ParseBool(features.ManagedInfraEnabled); err == nil {
		project.ManagedInfraEnabled = managedInfraEnabled
	}

	if stacksEnabled, err := strconv.ParseBool(features.StacksEnabled); err == nil {
		project.StacksEnabled = stacksEnabled
	}

	if previewEnvsEnabled, err := strconv.ParseBool(features.PreviewEnvironmentsEnabled); err == nil {
		project.PreviewEnvsEnabled = previewEnvsEnabled
	}

	if capiProvisionerEnabled, err := strconv.ParseBool(features.CapiProvisionerEnabled); err == nil {
		project.CapiProvisionerEnabled = capiProvisionerEnabled
	}

	_, err = c.Repo().Project().UpdateProject(project)

	if err != nil {
		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
		return
	}
}
