package hook_impl

import (
	"context"
	"fmt"

	"github.com/tidbcloud/dataflow-service/app/event_bus/publisher/center"
	"github.com/tidbcloud/dataflow-service/app/repo/backup"
	"github.com/tidbcloud/dataflow-service/app/repo/entity"
	"github.com/tidbcloud/dataflow-service/pkg/log"
	"github.com/tidbcloud/eventbus-datastruct/event"
	"gorm.io/gorm"
)

var (
	backupExportCommonMsg = "The export for the %s backup was %s."
	backupExportFailedMsg = "The export for the %s backup failed.\n%s"
)

var backupExportStatus2TypeMap = map[entity.BackupExportStatus]center.MsgStatusType{
	entity.BackupExportStatusPending:  center.PendingStatus,
	entity.BackupExportStatusRunning:  center.RunningStatus,
	entity.BackupExportStatusSuccess:  center.SuccessStatus,
	entity.BackupExportStatusFailed:   center.FailedStatus,
	entity.BackupExportStatusCanceled: center.TerminatedStatus,
}

var _ entity.DataflowEventHooker = &backupExportHooker{}

func NewBackupExportHooker(backupRepo backup.BackupRepo) entity.DataflowEventHooker {
	return &backupExportHooker{
		backupRepo: backupRepo,
	}
}

type backupExportHooker struct {
	backupRepo backup.BackupRepo
}

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

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

func (b *backupExportHooker) Type() entity.DataflowEventType {
	return entity.BackupExportDisplayType
}

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

	return backupExport
}

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

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

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

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

	return data
}

func (b *backupExportHooker) buildDetailMsg(tx *gorm.DB, value interface{}) *center.BackupExportMsg {
	backupExport := b.assertion(tx, value)

	backupExportMsg := &center.BackupExportMsg{
		BaseMsg: center.BaseMsg{
			Name:         backupExport.Name,
			Type:         center.BackupExport,
			Status:       backupExportStatus2TypeMap[backupExport.Status],
			HappenedAtMs: uint64(backupExport.UpdatedAt.UnixMilli()),
		},
		IDMsg: center.IDMsg{
			AuthorID:  backupExport.UserID.Uint64(),
			TenantID:  uint64(backupExport.TenantID),
			ProjectID: uint64(backupExport.ProjectID),
			ClusterID: uint64(backupExport.ClusterID),
		},
		BackupExportID: uint64(backupExport.ID),
	}

	return backupExportMsg
}

func (b *backupExportHooker) buildDisplayEventSummary(
	msgName string, backupExport *entity.BackupExport, msgStatus center.MsgStatusType,
) string {
	var (
		summary       string
		displayStatus event.EventStatus
	)
	backupName := "<unknown>"
	if backupExport.Backup != nil {
		backupName = backupExport.Backup.Name()
	} else {
		backup, _ := b.backupRepo.Load(context.Background(), backupExport.BackupID.Uint64())
		if backup != nil {
			backupName = backup.Name()
		}
	}

	if s, ok := center.MsgStatus2EventMap[msgStatus]; ok {
		displayStatus = s
	} else {
		displayStatus = event.EventStatusRunning
	}

	switch displayStatus {
	case event.EventStatusFailed:
		summary += fmt.Sprintf(backupExportFailedMsg, backupName, backupExport.StatusMsg)
	default:
		summary += fmt.Sprintf(backupExportCommonMsg, backupName, displayStatus)
	}

	return summary
}
