package migrations

import (
	"context"
	"reflect"
	"time"

	"github.com/grafana/grafana/pkg/infra/log"
	"github.com/grafana/grafana/pkg/infra/serverlock"
	"github.com/grafana/grafana/pkg/registry"
	"github.com/grafana/grafana/pkg/setting"
)

var logger = log.New("secret.migration")

const actionName = "secret migration task "

// SecretMigrationService is used to migrate legacy secrets to new unified secrets.
type SecretMigrationService interface {
	Migrate(ctx context.Context) error
}

type SecretMigrationProvider interface {
	registry.BackgroundService
	TriggerPluginMigration(ctx context.Context, toPlugin bool) error
}

type SecretMigrationProviderImpl struct {
	services                 []SecretMigrationService
	ServerLockService        *serverlock.ServerLockService
	migrateToPluginService   *MigrateToPluginService
	migrateFromPluginService *MigrateFromPluginService
}

func ProvideSecretMigrationProvider(
	cfg *setting.Cfg,
	serverLockService *serverlock.ServerLockService,
	dataSourceSecretMigrationService *DataSourceSecretMigrationService,
	migrateToPluginService *MigrateToPluginService,
	migrateFromPluginService *MigrateFromPluginService,
) *SecretMigrationProviderImpl {
	services := make([]SecretMigrationService, 0)
	services = append(services, dataSourceSecretMigrationService)
	// Plugin migration should always be last; should either migrate to or from, not both
	// This is because the migrateTo checks for use_plugin = true, in which case we should always
	// migrate by default to ensure users don't lose access to secrets. If migration has
	// already occurred, the migrateTo function will be called but it won't do anything
	if cfg.SectionWithEnvOverrides("secrets").Key("migrate_from_plugin").MustBool(false) {
		services = append(services, migrateFromPluginService)
	} else {
		services = append(services, migrateToPluginService)
	}

	return &SecretMigrationProviderImpl{
		ServerLockService:        serverLockService,
		services:                 services,
		migrateToPluginService:   migrateToPluginService,
		migrateFromPluginService: migrateFromPluginService,
	}
}

func (s *SecretMigrationProviderImpl) Run(ctx context.Context) error {
	return s.Migrate(ctx)
}

// Migrate Run migration services. This will block until all services have exited.
// This should only be called once at startup
func (s *SecretMigrationProviderImpl) Migrate(ctx context.Context) error {
	// Start migration services.
	err := s.ServerLockService.LockExecuteAndRelease(ctx, actionName, time.Minute*10, func(context.Context) {
		for _, service := range s.services {
			serviceName := reflect.TypeOf(service).String()
			logger.Debug("Starting secret migration service", "service", serviceName)
			err := service.Migrate(ctx)
			if err != nil {
				logger.Error("Stopped secret migration service", "service", serviceName, "reason", err)
			}
			logger.Debug("Finished secret migration service", "service", serviceName)
		}
	})
	if err != nil {
		logger.Error("Server lock for secret migration already exists")
	}
	return nil
}

// TriggerPluginMigration Kick off a migration to or from the plugin. This will block until all services have exited.
func (s *SecretMigrationProviderImpl) TriggerPluginMigration(ctx context.Context, toPlugin bool) error {
	// Don't migrate if there is already one happening
	return s.ServerLockService.LockExecuteAndRelease(ctx, actionName, time.Minute*10, func(context.Context) {
		var err error
		if toPlugin {
			err = s.migrateToPluginService.Migrate(ctx)
		} else {
			err = s.migrateFromPluginService.Migrate(ctx)
		}
		if err != nil {
			direction := "from_plugin"
			if toPlugin {
				direction = "to_plugin"
			}
			logger.Error("Failed to migrate plugin secrets", "direction", direction, "error", err.Error())
		}
	})
}
