package opworker

import (
	"context"
	"sync"

	"github.com/tidbcloud/account/commons/database"

	"github.com/tidbcloud/account/commons/config"

	"github.com/pingcap/errors"
	"github.com/tidbcloud/account/internel/dao/entity"
	mailv2 "github.com/tidbcloud/pkgv2/mail/v2"
	"github.com/tidbcloud/pkgv2/worker"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

type Manager struct {
	backgroundWorker sync.Map
	lck              sync.Mutex
}

func New(mailFacade mailv2.Facade) *Manager {
	return &Manager{
		backgroundWorker: sync.Map{},
		lck:              sync.Mutex{},
	}
}

func (c *Manager) StartBackgroundWorkers() error {
	if !config.IsLocalEnv() {
		var tenants []entity.Tenant
		err := database.Transaction(func(tx *gorm.DB) error {
			return entity.NewTenantQuerySet(tx).StatusEq(entity.TenantStatusActive).All(&tenants)
		})
		if err != nil {
			return errors.Annotate(err, "retrieve active tenants failed")
		}
		for _, t := range tenants {
			if err := c.StartBackgroundWorkerForTenant(t.ID); err != nil {
				return err
			}
		}
	}

	w, err := NewGlobalBgWorker(c)
	if err != nil {
		return err
	}

	w.Start()
	return nil
}

func (c *Manager) StartBackgroundWorkerForTenant(tenantID entity.ID) error {
	c.lck.Lock()
	defer c.lck.Unlock()

	ctx := context.Background()

	_, ok := c.backgroundWorker.Load(tenantID.Uint64())
	if ok {
		//	tilog.Info(ctx, "background worker already started, skip", zap.Uint64("tenantID", tenantID.Uint64()))
		return nil
	}
	tilog.Info(ctx, "starting background worker", zap.Uint64("tenantID", tenantID.Uint64()))

	w, err := NewBgWorker(tenantID)
	if err != nil {
		return errors.Annotate(err, "opworker")
	}
	c.backgroundWorker.Store(tenantID.Uint64(), w)
	w.Start()
	return nil
}

func (c *Manager) StopBackgroundWorkerForTenant(tenantID entity.ID) error {
	c.lck.Lock()
	defer c.lck.Unlock()

	ctx := context.Background()

	tilog.Info(ctx, "stopping background worker", zap.Uint64("tenantID", tenantID.Uint64()))
	w, ok := c.backgroundWorker.Load(tenantID)
	if ok {
		c.backgroundWorker.Delete(tenantID)
		go func() {
			w.(worker.Worker).Stop()
			tilog.Info(ctx, "background worker is stopped", zap.Uint64("tenantID", tenantID.Uint64()))
		}()
		return nil
	}
	return errors.Errorf("tenant %d worker is not found", tenantID)
}

func (c *Manager) GetBackgroundWorker(tenantID entity.ID) worker.Worker {
	w, found := c.backgroundWorker.Load(tenantID)
	if !found {
		return nil
	}
	return w.(worker.Worker)
}
