package appinstaller

import (
	"context"
	"time"

	"k8s.io/apimachinery/pkg/runtime/schema"
	k8srequest "k8s.io/apiserver/pkg/endpoints/request"
	"k8s.io/klog/v2"

	grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
	"github.com/grafana/grafana/pkg/services/apiserver/builder"
	"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
	"github.com/grafana/grafana/pkg/services/apiserver/options"
	"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
)

// NewDualWriter creates a dual writer for the given group resource using the provided configuration
func NewDualWriter(
	_ context.Context,
	gr schema.GroupResource,
	storageOpts *options.StorageOptions,
	legacy grafanarest.Storage,
	storage grafanarest.Storage,
	kvStore grafanarest.NamespacedKVStore,
	lock serverLock,
	namespaceMapper request.NamespaceMapper,
	dualWriteService dualwrite.Service,
	dualWriterMetrics *grafanarest.DualWriterMetrics,
	builderMetrics *builder.BuilderMetrics,
) (grafanarest.Storage, error) {
	// Dashboards + Folders may be managed (depends on feature toggles and database state)
	if dualWriteService != nil && dualWriteService.ShouldManage(gr) {
		return dualWriteService.NewStorage(gr, legacy, storage) // eventually this can replace this whole function
	}

	key := gr.String() // ${resource}.{group} eg playlists.playlist.grafana.app

	// Get the option from custom.ini/command line
	// when missing this will default to mode zero (legacy only)
	var mode = grafanarest.DualWriterMode(0)

	var (
		dualWriterPeriodicDataSyncJobEnabled bool
		dualWriterMigrationDataSyncDisabled  bool
		dataSyncerInterval                   = time.Hour
		dataSyncerRecordsLimit               = 1000
	)

	resourceConfig, resourceExists := storageOpts.UnifiedStorageConfig[key]
	if resourceExists {
		mode = resourceConfig.DualWriterMode
		dualWriterPeriodicDataSyncJobEnabled = resourceConfig.DualWriterPeriodicDataSyncJobEnabled
		dualWriterMigrationDataSyncDisabled = resourceConfig.DualWriterMigrationDataSyncDisabled
		dataSyncerInterval = resourceConfig.DataSyncerInterval
		dataSyncerRecordsLimit = resourceConfig.DataSyncerRecordsLimit
	}

	// Force using storage only -- regardless of internal synchronization state
	if mode == grafanarest.Mode5 {
		builderMetrics.RecordDualWriterModes(gr.Resource, gr.Group, mode, grafanarest.Mode5)
		return storage, nil
	}

	// Moving from one version to the next can only happen after the previous step has
	// successfully synchronized.
	requestInfo := getRequestInfo(gr, namespaceMapper)

	syncerCfg := &grafanarest.SyncerConfig{
		Kind:                   key,
		RequestInfo:            requestInfo,
		Mode:                   mode,
		SkipDataSync:           dualWriterMigrationDataSyncDisabled,
		LegacyStorage:          legacy,
		Storage:                storage,
		ServerLockService:      lock,
		DataSyncerInterval:     dataSyncerInterval,
		DataSyncerRecordsLimit: dataSyncerRecordsLimit,
	}

	ctx := context.Background()
	// This also sets the currentMode on the syncer config.
	currentMode, err := grafanarest.SetDualWritingMode(ctx, kvStore, syncerCfg, dualWriterMetrics)
	if err != nil {
		return nil, err
	}

	builderMetrics.RecordDualWriterModes(gr.Resource, gr.Group, mode, currentMode)

	switch currentMode {
	case grafanarest.Mode0:
		return legacy, nil
	case grafanarest.Mode4, grafanarest.Mode5:
		return storage, nil
	default:
	}

	if dualWriterPeriodicDataSyncJobEnabled {
		// The mode might have changed in SetDualWritingMode, so apply current mode first.
		syncerCfg.Mode = currentMode
		if err := grafanarest.StartPeriodicDataSyncer(ctx, syncerCfg, dualWriterMetrics); err != nil {
			return nil, err
		}
	}

	// when unable to use
	if currentMode != mode {
		klog.Warningf("Requested DualWrite mode: %d, but using %d for %+v", mode, currentMode, gr)
	}
	return dualwrite.NewStaticStorage(gr, currentMode, legacy, storage)
}

func getRequestInfo(gr schema.GroupResource, namespaceMapper request.NamespaceMapper) *k8srequest.RequestInfo {
	return &k8srequest.RequestInfo{
		APIGroup:  gr.Group,
		Resource:  gr.Resource,
		Name:      "",
		Namespace: namespaceMapper(int64(1)),
	}
}
