package state

import (
	"context"
	"encoding/json"
	"net/http"

	"github.com/porter-dev/porter/api/server/shared"
	"github.com/porter-dev/porter/api/server/shared/apierrors"
	"github.com/porter-dev/porter/api/types"
	"github.com/porter-dev/porter/internal/models"
	"github.com/porter-dev/porter/internal/telemetry"
	"github.com/porter-dev/porter/provisioner/integrations/redis_stream"
	"github.com/porter-dev/porter/provisioner/server/config"
)

type DeleteResourceHandler struct {
	Config           *config.Config
	decoderValidator shared.RequestDecoderValidator
}

func NewDeleteResourceHandler(
	config *config.Config,
) *DeleteResourceHandler {
	return &DeleteResourceHandler{
		Config:           config,
		decoderValidator: shared.NewDefaultRequestDecoderValidator(config.Logger, config.Alerter),
	}
}

func (c *DeleteResourceHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx, span := telemetry.NewSpan(r.Context(), "serve-delete-resource")
	defer span.End()

	// read the infra from the attached scope
	infra, _ := ctx.Value(types.InfraScope).(*models.Infra)
	operation, _ := ctx.Value(types.OperationScope).(*models.Operation)

	// update the operation to indicate completion
	operation.Status = "completed"

	operation, err := c.Config.Repo.Infra().UpdateOperation(operation)
	if err != nil {
		apierrors.HandleAPIError(c.Config.Logger, c.Config.Alerter, w, r, apierrors.NewErrInternal(err), true)
		return
	}

	// push to the operation stream
	err = redis_stream.SendOperationCompleted(c.Config.RedisClient, infra, operation)

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

	// push to the global stream
	err = redis_stream.PushToGlobalStream(c.Config.RedisClient, infra, operation, "destroyed")

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

	// update the infra to indicate deletion
	infra.Status = "deleted"

	infra, err = c.Config.Repo.Infra().UpdateInfra(infra)

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

	// switch on the kind of resource and write the corresponding objects to the database
	switch infra.Kind {
	case types.InfraECR, types.InfraGCR, types.InfraGAR, types.InfraDOCR, types.InfraACR:
		_, err = deleteRegistry(c.Config, infra, operation)
	case types.InfraEKS, types.InfraDOKS, types.InfraGKE, types.InfraAKS:
		_, err = deleteCluster(c.Config, infra, operation)
	case types.InfraRDS:
		_, err = deleteDatabase(c.Config, infra, operation)
	case types.InfraS3:
		err = deleteS3Bucket(ctx, c.Config, infra, operation)
	}

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

func deleteRegistry(config *config.Config, infra *models.Infra, operation *models.Operation) (*models.Registry, error) {
	reg, err := config.Repo.Registry().ReadRegistryByInfraID(infra.ProjectID, infra.ID)
	if err != nil {
		return nil, err
	}

	err = config.Repo.Registry().DeleteRegistry(reg)

	if err != nil {
		return nil, err
	}

	return reg, nil
}

func deleteCluster(config *config.Config, infra *models.Infra, operation *models.Operation) (*models.Cluster, error) {
	cluster, err := config.Repo.Cluster().ReadClusterByInfraID(infra.ProjectID, infra.ID)
	if err != nil {
		return nil, err
	}

	err = config.Repo.Cluster().DeleteCluster(cluster)

	if err != nil {
		return nil, err
	}

	return cluster, nil
}

func deleteDatabase(config *config.Config, infra *models.Infra, operation *models.Operation) (*models.Database, error) {
	database, err := config.Repo.Database().ReadDatabaseByInfraID(infra.ProjectID, infra.ID)
	if err != nil {
		return nil, err
	}

	err = config.Repo.Database().DeleteDatabase(database.ProjectID, database.ClusterID, database.ID)

	if err != nil {
		return nil, err
	}

	// TODO: add delete env group here

	return database, nil
}

func deleteS3Bucket(ctx context.Context, config *config.Config, infra *models.Infra, operation *models.Operation) error {
	lastApplied := make(map[string]interface{})

	err := json.Unmarshal(operation.LastApplied, &lastApplied)
	if err != nil {
		return err
	}

	return deleteS3EnvGroup(ctx, config, infra, lastApplied)
}
