package cron

import (
	"context"
	"fmt"
	"time"

	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/domain/server"
	"github.com/tidbcloud/cluster-service-ng/pkg/storage/repo"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

type CleanSharedPoolJob struct {
	ngService  server.Service
	lockRepo   repo.LockRepo
	expression string
}

func NewCleanSharedPoolJob(ngService server.Service, lockRepo repo.LockRepo, expression string) *CleanSharedPoolJob {
	return &CleanSharedPoolJob{
		ngService:  ngService,
		lockRepo:   lockRepo,
		expression: expression,
	}
}

func (j *CleanSharedPoolJob) Name() string {
	return "CleanSharedPoolJob"
}

func (j *CleanSharedPoolJob) CronExpression() string {
	return j.expression
}

func (j *CleanSharedPoolJob) Execute(ctx context.Context) error {
	// list all shared pools
	sharedPools, err := j.ngService.ListSharedPools(ctx, nil, nil)
	if err != nil {
		tilog.Error(ctx, "failed to list shared pools", zap.Error(err))
		return fmt.Errorf("clean shared pool job failed: %w", err)
	}
	curr := time.Now()

	for _, sharedPool := range sharedPools {
		// clean up the shared pool
		if sharedPool.CreatedAt.After(curr.Add(-6 * time.Hour)) {
			tilog.Info(ctx, "shared pool create in less 6 hour, skip clean", zap.String("sharedPoolId", sharedPool.GetId()))
			continue
		}
		tidbclusters, err := j.ngService.ListTiDBClusters(ctx, sharedPool.GetId(), "")
		if err != nil {
			tilog.Error(ctx, "failed to list tidb clusters for shared pool, skip clean", zap.String("sharedPoolId", sharedPool.GetId()), zap.Error(err))
			continue
		}

		if len(tidbclusters) > 0 {
			tilog.Info(ctx, "shared pool has tidb clusters, skip clean", zap.String("sharedPoolId", sharedPool.GetId()), zap.Int("tidbClusterCount", len(tidbclusters)))
			continue
		}

		// acquire lock
		unlockFunc, err := j.lock(ctx, sharedPool.GetId())
		if err != nil {
			tilog.Error(ctx, "failed to acquire lock for shared pool, will retry later", zap.String("sharedPoolId", sharedPool.GetId()), zap.Error(err))
			continue
		}

		_, err = j.ngService.DeleteSharedPool(ctx, sharedPool.GetId(), true)
		if err != nil {
			tilog.Error(ctx, "failed to delete shared pool, will retry later", zap.String("sharedPoolId", sharedPool.GetId()), zap.Error(err))
		}

		tilog.Info(ctx, "shared pool deleted", zap.String("sharedPoolId", sharedPool.GetId()))

		// unlock even if delete failed
		unlockErr := unlockFunc()
		if unlockErr != nil {
			tilog.Error(ctx, "failed to release lock after delete failed", zap.String("sharedPoolId", sharedPool.GetId()), zap.Error(unlockErr))
		}
	}

	return nil
}

func (s *CleanSharedPoolJob) lock(ctx context.Context, key string) (func() error, error) {
	lock := &entity.Lock{
		Key:         key,
		TimeoutSecs: 10,
	}

	unlockFunc, err := s.lockRepo.Lock(ctx, lock)
	if err != nil {
		return nil, err
	}
	return unlockFunc, nil
}
