package util

import (
	"context"
	"fmt"
	"sync"
	"time"

	"github.com/tidbcloud/core-portal/internal/central/pkg/gormutil"
	entity "github.com/tidbcloud/core-svc/model"
	"github.com/tidbcloud/pkgv2/log"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

type NotificationManager interface {
	SendNotificationOnce(ctx context.Context, tx *gorm.DB, tenantID entity.ID, scene entity.NotificationScene, identifier string, fn func() error) error
	ClearNotificationIdentifier(ctx context.Context, tx *gorm.DB, tenantID entity.ID, scene entity.NotificationScene, identifier string) error
	FillNotificationIdentifier(ctx context.Context, tx *gorm.DB, tenantID entity.ID, scene entity.NotificationScene, identifier string) error
}

func FakeSendNotificationOnce(ctx context.Context, tx *gorm.DB, tenantID entity.ID, scene entity.NotificationScene, identifier string, fn func() error) error {
	return fn()
}

type notificationMgrImp struct {
}

var (
	notificationMgr NotificationManager = (*notificationMgrImp)(nil)
	once            sync.Once
)

func NewNotificationMgr() NotificationManager {
	once.Do(func() {
		notificationMgr = &notificationMgrImp{}
	})

	return notificationMgr
}

func (mgr *notificationMgrImp) SendNotificationOnce(ctx context.Context, tx *gorm.DB, tenantID entity.ID, scene entity.NotificationScene, identifier string, fn func() error) error {
	logger := log.WithContextL(ctx).With(
		zap.String("scene", string(scene)), zap.String("identifier", identifier),
		zap.Uint64("tenantID", tenantID.Uint64()),
	)

	var record entity.EmailReminder
	err := entity.NewEmailReminderQuerySet(tx).TenantIDEq(tenantID).SceneEq(scene).IdentifierEq(identifier).One(&record)
	if err == nil {
		logger.Info("email already sent, skip.")
		return nil
	} else if err != gorm.ErrRecordNotFound {
		return fmt.Errorf("load email reminder failed: %w", err)
	}

	record = entity.EmailReminder{
		TenantID:   tenantID,
		Scene:      scene,
		Identifier: identifier,
	}
	if err := record.Create(tx); err != nil {
		if gormutil.IsDuplicatedError(err) {
			logger.Info("emailReminder duplicated")
			return nil
		}
		return fmt.Errorf("create email reminder failed: %w", err)
	}
	logger.Info("emailReminder created")

	err = fn()
	if err != nil {
		logger.Error("failed to send email", zap.Error(err))
	} else {
		logger.Info("send email", zap.Time("time", time.Now().UTC()))
	}
	return err
}

func (mgr *notificationMgrImp) ClearNotificationIdentifier(ctx context.Context, tx *gorm.DB, tenantID entity.ID, scene entity.NotificationScene, identifier string) error {
	err := entity.NewEmailReminderQuerySet(tx).TenantIDEq(tenantID).SceneEq(scene).IdentifierEq(identifier).Delete()
	if err != nil {
		return fmt.Errorf("clear notificationIdentifier failed: %w", err)
	}
	return nil
}

func (mgr *notificationMgrImp) FillNotificationIdentifier(ctx context.Context, tx *gorm.DB, tenantID entity.ID, scene entity.NotificationScene, identifier string) error {
	logger := log.WithContextL(ctx).With(
		zap.String("scene", string(scene)), zap.String("identifier", identifier),
		zap.Uint64("tenantID", tenantID.Uint64()),
	)

	var record entity.EmailReminder
	err := entity.NewEmailReminderQuerySet(tx).TenantIDEq(tenantID).SceneEq(scene).IdentifierEq(identifier).One(&record)
	if err == gorm.ErrRecordNotFound {
		record = entity.EmailReminder{
			TenantID:   tenantID,
			Scene:      scene,
			Identifier: identifier,
		}

		logger.Info("notification not exist, so we create one")
		return record.Create(tx)
	}

	if err != nil {
		logger.Error("failed to query notification", zap.Error(err))
	}
	return err
}
