package entity

import (
	"context"
	"sync"

	"github.com/tidbcloud/dataflow-service/pkg/log"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

type DataflowEventType string

const (
	BackupDisplayType        DataflowEventType = "BackupCluster"
	BackupExportDisplayType  DataflowEventType = "ExportBackup"
	RestoreDisplayType       DataflowEventType = "RestoreFromBackup"
	ChangefeedDisplayType    DataflowEventType = "RunChangefeed"
	ImportDisplayType        DataflowEventType = "ImportData"
	CompactBackupDisplayType DataflowEventType = "CompactBackup"
	CRRFDisplayType          DataflowEventType = "CRRFSetting"
)

const (
	CreateChangefeed DataflowEventType = "CreateChangefeed"
	PauseChangefeed  DataflowEventType = "PauseChangefeed"
	EditChangefeed   DataflowEventType = "EditChangefeed"
	ResumeChangefeed DataflowEventType = "ResumeChangefeed"
	ScaleChangefeed  DataflowEventType = "ScaleChangefeed"
	DeleteChangefeed DataflowEventType = "DeleteChangefeed"
	FailedChangefeed DataflowEventType = "FailedChangefeed"
)

type DataflowEventHooker interface {
	Type() DataflowEventType
	BeforeSave(db *gorm.DB, value interface{})
	AfterSave(db *gorm.DB, value interface{})
}

type DataflowEventHookerManager interface {
	Register(hook DataflowEventHooker)
	RunBeforeSave(eventType DataflowEventType, tx *gorm.DB, v interface{})
	RunAfterSave(eventType DataflowEventType, tx *gorm.DB, v interface{})
}

// note that this is a singleton FIXME: this is not a good design, we should use dependency injection instead
var dataflowEventHookerManager DataflowEventHookerManager

func GetEventHookerManager() DataflowEventHookerManager {
	return dataflowEventHookerManager
}

type dataflowEventHookerManagerImpl struct {
	lck sync.Mutex

	logger  *zap.Logger
	hookers map[DataflowEventType][]DataflowEventHooker
}

func NewDataflowEventHookerManager() DataflowEventHookerManager {
	dataflowEventHookerManager = &dataflowEventHookerManagerImpl{
		hookers: make(map[DataflowEventType][]DataflowEventHooker),
		logger:  log.GetLogger(context.Background()).Named("dataflowEventHookerManager"),
	}
	return dataflowEventHookerManager
}

func (manager *dataflowEventHookerManagerImpl) Register(hook DataflowEventHooker) {
	manager.lck.Lock()
	defer manager.lck.Unlock()

	manager.hookers[hook.Type()] = append(manager.hookers[hook.Type()], hook)
	manager.logger.Info("Register Dataflow Event Hook", zap.Any("type", hook.Type()))
}

func (manager *dataflowEventHookerManagerImpl) RunBeforeSave(eventType DataflowEventType, tx *gorm.DB, v interface{}) {
	defer func() {
		if err := recover(); err != nil {
			manager.logger.Error("runBeforeSave panic occurred",
				zap.Any("err", err))
			return
		}
	}()
	for _, hooker := range manager.hookers[eventType] {
		hooker.BeforeSave(tx, v)
	}
}

func (manager *dataflowEventHookerManagerImpl) RunAfterSave(eventType DataflowEventType, tx *gorm.DB, v interface{}) {
	defer func() {
		if err := recover(); err != nil {
			manager.logger.Error("runAfterSave panic occurred",
				zap.Any("err", err))
			return
		}
	}()

	for _, hooker := range manager.hookers[eventType] {
		hooker.AfterSave(tx, v)
	}
}
