package apps

import (
	"context"
	"fmt"

	"github.com/acorn-io/mink/pkg/stores"
	"github.com/acorn-io/mink/pkg/types"
	apiv1 "github.com/acorn-io/runtime/pkg/apis/api.acorn.io/v1"
	v1 "github.com/acorn-io/runtime/pkg/apis/internal.acorn.io/v1"
	"github.com/acorn-io/runtime/pkg/controller/jobs"
	kclient "github.com/acorn-io/runtime/pkg/k8sclient"
	"github.com/acorn-io/runtime/pkg/labels"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/apiserver/pkg/registry/rest"
	"k8s.io/client-go/util/retry"
	"k8s.io/utils/strings/slices"
	"sigs.k8s.io/controller-runtime/pkg/client"
)

func NewIgnoreCleanup(c client.WithWatch) rest.Storage {
	return stores.NewBuilder(c.Scheme(), &apiv1.IgnoreCleanup{}).
		WithCreate(&ignoreCleanupStrategy{
			client: c,
		}).WithValidateName(nestedValidator{}).Build()
}

type ignoreCleanupStrategy struct {
	client client.WithWatch
}

func (s *ignoreCleanupStrategy) Create(ctx context.Context, obj types.Object) (types.Object, error) {
	ri, _ := request.RequestInfoFrom(ctx)

	if ri.Name == "" || ri.Namespace == "" {
		return obj, nil
	}

	err := retry.RetryOnConflict(retry.DefaultRetry, func() error {
		// Use app instance here because in Manager this request is forwarded to the workload cluster.
		// The app validation logic should not run there.
		app := &v1.AppInstance{}
		err := s.client.Get(ctx, kclient.ObjectKey{Namespace: ri.Namespace, Name: ri.Name}, app)
		if apierrors.IsNotFound(err) {
			// See if this is a public name
			appList := &v1.AppInstanceList{}
			listErr := s.client.List(ctx, appList, client.MatchingLabels{labels.AcornPublicName: ri.Name}, client.InNamespace(ri.Namespace))
			if listErr != nil {
				return listErr
			}
			if len(appList.Items) != 1 {
				// return the NotFound error we got originally
				return err
			}
			app = &appList.Items[0]
		} else if err != nil {
			return err
		}

		if app.DeletionTimestamp.IsZero() {
			return apierrors.NewBadRequest(fmt.Sprintf("cannot force delete app %s because it is not being deleted", app.Name))
		}

		// If the app has the destroy job finalizer, remove it to force delete
		if idx := slices.Index(app.Finalizers, jobs.DestroyJobFinalizer); idx >= 0 {
			app.Finalizers = append(app.Finalizers[:idx], app.Finalizers[idx+1:]...)

			if err = s.client.Update(ctx, app); err != nil {
				return err
			}
		}

		return nil
	})

	return obj, err
}

func (s *ignoreCleanupStrategy) New() types.Object {
	return &apiv1.IgnoreCleanup{}
}
