package hook_impl

import (
	"github.com/tidbcloud/dataflow-service/app/event_bus/publisher/center"
	"github.com/tidbcloud/dataflow-service/app/repo/entity"
	"github.com/tidbcloud/dataflow-service/internal/bizutils"
	"github.com/tidbcloud/dataflow-service/pkg/log"
	"github.com/tidbcloud/eventbus-datastruct/event"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

var backupStatus2TypeMap = map[entity.BackupStatus]center.MsgStatusType{
	entity.BackupStatusPending:    center.PendingStatus,
	entity.BackupStatusRunning:    center.RunningStatus,
	entity.BackupStatusSuccess:    center.SuccessStatus,
	entity.BackupStatusFailed:     center.FailedStatus,
	entity.BackupStatusSkipped:    center.SkippedStatus,
	entity.BackupStatusTerminated: center.TerminatedStatus,
}

func GetBackupHooker() entity.DataflowEventHooker {
	return bh
}

var bh = &backupHooker{}

type backupHooker struct{}

func (b *backupHooker) BeforeSave(tx *gorm.DB, v interface{}) {
	BaseBeforeSave(tx, v, isNewlyCreated, b.checkEntityStatusChanged)
}

func (b *backupHooker) AfterSave(tx *gorm.DB, v interface{}) {
	BaseAfterSave(tx, v, b.convertToDisplayEvent)
}

func (b *backupHooker) Type() entity.DataflowEventType {
	return entity.BackupDisplayType
}

func (b *backupHooker) assertion(tx *gorm.DB, value interface{}) *entity.Backup {
	logger := log.GetLogger(tx.Statement.Context)
	backup, ok := value.(*entity.Backup)
	if !ok {
		logger.Warn("value is not entity.Backup")
		return nil
	}

	return backup
}

func (b *backupHooker) checkEntityStatusChanged(tx *gorm.DB, value interface{}) bool {
	backup := b.assertion(tx, value)
	if backup == nil {
		return false
	}

	var currentRecord entity.Backup
	if err := tx.First(&currentRecord, backup.ID).Error; err != nil {
		return false
	}
	return currentRecord.Status != backup.Status
}

func (b *backupHooker) convertToDisplayEvent(tx *gorm.DB, value interface{}) *event.DisplayEvent {
	msg := b.buildDetailMsg(tx, value)
	if msg == nil {
		return nil
	}

	data := center.BuildEventBasicInfos(msg.BackupID, msg.HappenedAtMs, msg.IDMsg)
	data.Summary = center.BuildDisplayEventSummary(msg.Name, msg.Type, msg.Status)
	data.DisplayEventType = string(b.Type())
	data.DisplayStatus = center.MsgStatus2EventMap[msg.Status]
	data.Detail = msg

	return data
}

//nolint:funlen
func (b *backupHooker) buildDetailMsg(tx *gorm.DB, value interface{}) *center.BackupMsg {
	backup := b.assertion(tx, value)

	backupMsg := &center.BackupMsg{
		BaseMsg: center.BaseMsg{
			Status:       backupStatus2TypeMap[backup.Status],
			HappenedAtMs: uint64(backup.UpdatedAt.UnixMilli()),
		},
		IDMsg: center.IDMsg{
			AuthorID:  0,
			TenantID:  uint64(backup.TenantID),
			ProjectID: uint64(backup.ProjectID),
			ClusterID: uint64(backup.ClusterID),
		},
		BackupID:         uint64(backup.ID),
		Size:             backup.BackupSize,
		BackupScheduleID: uint64(backup.BackupScheduleID),
		BucketID:         uint64(backup.BucketID),
	}

	var schedule entity.BackupSchedule
	err := entity.NewBackupScheduleQuerySet(tx).IDEq(backup.BackupScheduleID).One(&schedule)
	if err != nil {
		return nil
	}

	// this is temporary solution, update will be available soon.
	if backupStatus2TypeMap[backup.Status] == center.PendingStatus {
		var newBackup entity.Backup
		err := entity.NewBackupQuerySet(tx).IDEq(backup.ID).One(&newBackup)
		if err != nil {
			return nil
		}
		backup = &newBackup
	}

	// update info of name
	backupMsg.Name = schedule.BackupName(backup.CreatedAt)
	// default use schedule's creator as author
	authorID := uint64(schedule.UserID)
	// try get current api operation user from context
	u, err := bizutils.GetUserFromContext(tx.Statement.Context)
	if err != nil {
		log.GetLogger(tx.Statement.Context).Warn("get user from context failed", zap.Error(err))
	} else {
		authorID = u.ID
	}

	// update info of type
	if schedule.Name == center.FinalBackupName {
		backupMsg.Type = center.FinalBackup
	} else {
		switch schedule.Type {
		case entity.BackupTypeManual:
			backupMsg.Type = center.ManualBackup
			backupMsg.AuthorID = authorID
		default:
			backupMsg.Type = center.AutoBackup
		}
	}

	return backupMsg
}
