package shorturl

import (
	"context"
	"errors"
	"fmt"

	k8serrors "k8s.io/apimachinery/pkg/api/errors"
	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/apiserver/pkg/registry/rest"
	"sigs.k8s.io/structured-merge-diff/v6/fieldpath"

	"github.com/grafana/grafana-app-sdk/k8s/apiserver"
	"github.com/grafana/grafana-app-sdk/logging"
	shorturl "github.com/grafana/grafana/apps/shorturl/pkg/apis/shorturl/v1beta1"
	"github.com/grafana/grafana/pkg/apimachinery/identity"
	"github.com/grafana/grafana/pkg/services/shorturls"
)

type statusDualWriter struct {
	gv     schema.GroupVersion
	status *apiserver.StatusREST
	legacy *legacyStorage
}

var (
	_ rest.Patcher             = (*statusDualWriter)(nil)
	_ rest.Storage             = (*statusDualWriter)(nil)
	_ rest.ResetFieldsStrategy = (*statusDualWriter)(nil)
)

// Destroy implements rest.Storage.
func (s *statusDualWriter) Destroy() {}

// New implements rest.Storage.
func (s *statusDualWriter) New() runtime.Object {
	return s.legacy.New()
}

// Get implements rest.Patcher.
func (s *statusDualWriter) Get(ctx context.Context, name string, options *v1.GetOptions) (runtime.Object, error) {
	return s.legacy.Get(ctx, name, options)
}

// Update implements rest.Patcher.
func (s *statusDualWriter) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *v1.UpdateOptions) (runtime.Object, bool, error) {
	requester, err := identity.GetRequester(ctx)
	if err != nil {
		return nil, false, err
	}

	shortURL, err := s.legacy.service.GetShortURLByUID(ctx, requester, name)
	if err != nil || shortURL == nil {
		if errors.Is(err, shorturls.ErrShortURLNotFound) || err == nil {
			err = k8serrors.NewNotFound(shorturl.ShortURLKind().GroupVersionResource().GroupResource(), name)
		}
		return nil, false, err
	}

	// This ignores the incoming and updates it directly
	err = s.legacy.service.UpdateLastSeenAt(ctx, shortURL)
	if err != nil {
		return nil, false, err
	}

	getter := func(getter rest.Getter) (*shorturl.ShortURL, error) {
		obj, err := getter.Get(ctx, name, &v1.GetOptions{})
		if err != nil {
			return nil, err
		}
		val, ok := obj.(*shorturl.ShortURL)
		if !ok {
			return nil, fmt.Errorf("expected ShortURL but got %T", obj)
		}
		return val, nil
	}

	legacy, err := getter(s.legacy)
	if err != nil {
		return nil, false, err // unable to get legacy object
	}

	unified, err := getter(s.status)
	if err != nil {
		logging.FromContext(ctx).Warn("unable to read unified status", "error", err)
		return legacy, false, nil
	}

	// Use the same status from legacy in unified
	unified.Status = legacy.Status

	_, _, err = s.status.Update(ctx, name, rest.DefaultUpdatedObjectInfo(unified), createValidation, updateValidation, false, options)
	if err != nil {
		logging.FromContext(ctx).Warn("error updating unified status", "error", err)
	}

	return legacy, false, err
}

// GetResetFields implements rest.ResetFieldsStrategy
func (s *statusDualWriter) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set {
	fields := map[fieldpath.APIVersion]*fieldpath.Set{
		fieldpath.APIVersion(s.gv.String()): fieldpath.NewSet(
			fieldpath.MakePathOrDie("spec"),
			fieldpath.MakePathOrDie("metadata"),
		),
	}
	return fields
}
