package cluster

import (
	"context"
	"time"

	"github.com/tidbcloud/pkgv2/db"
	"gorm.io/gorm"

	globaldedicatedv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/dedicated/v1"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/domain/repository/cluster"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

const (
	ScaleOperationTrackingRecentDuration = 6 * time.Hour
)

type operationRepoImpl struct {
	db.Provider
}

func NewOperationRepo(provider db.Provider) cluster.OperationRepo {
	return &operationRepoImpl{
		Provider: provider,
	}
}

func buildClusterOperationGetter(tx *gorm.DB) *gorm.DB {
	return tx.Model(&schema.ClusterOperation{})
}

func (i *operationRepoImpl) LoadRecentScaleOperations(
	ctx context.Context,
	clusterID uint64,
	componentIds []uint64,
) (map[globaldedicatedv1.ComponentType][]*entity.ClusterOperation, bizerr.BizError) {
	out := make(map[globaldedicatedv1.ComponentType][]*entity.ClusterOperation)

	if len(componentIds) == 0 {
		return out, nil
	}

	rdb := i.ReadDB(ctx)
	var operations []*schema.ClusterOperation
	if err := buildClusterOperationGetter(rdb).
		Where("cluster_id = ?", clusterID).
		Where("type = ?", globaldedicatedv1.ClusterOperationType_SCALE).
		Where("created_at >= ?", time.Now().Add(-1*ScaleOperationTrackingRecentDuration)).
		Find(&operations).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load cluster operations")
	}

	componentIDMap := make(map[uint64]struct{})
	for _, id := range componentIds {
		componentIDMap[id] = struct{}{}
	}

	for _, op := range operations {
		operation, err := clusterOperationModelToEn(op)
		if err != nil {
			return nil, err
		}

		if operation.Operation != nil {
			scaleOperation, ok := operation.Operation.(*entity.ScaleOperation)
			if !ok {
				return nil, bizerr.New(bizerr.ErrCodeUnknown, "operation is not scale operation")
			}
			if _, ok := componentIDMap[scaleOperation.ComponentID]; ok {
				out[scaleOperation.ComponentType] = append(out[scaleOperation.ComponentType], operation)
			}
		}
	}
	return out, nil
}

func (i *operationRepoImpl) Save(ctx context.Context, operation *entity.ClusterOperation) bizerr.BizError {
	wdb := i.WriteDB(ctx)

	operationModel, err := clusterOperationEnToModel(operation)
	if err != nil {
		return err
	}
	rawErr := wdb.Save(operationModel).Error
	if rawErr != nil {
		return bizerr.WrapGormError(rawErr, "save cluster operation")
	}

	return nil
}

func (i *operationRepoImpl) Delete(ctx context.Context, clusterID uint64) bizerr.BizError {
	wdb := i.WriteDB(ctx)

	rawErr := wdb.Where("`cluster_id` = ?", clusterID).Delete(&schema.ClusterOperation{}).Error
	if rawErr != nil {
		return bizerr.WrapGormError(rawErr, "save cluster operation")
	}

	return nil
}

func (i *operationRepoImpl) LoadUnfinishedOperations(
	ctx context.Context,
	clusterID uint64,
) (map[globaldedicatedv1.ClusterOperationType]*entity.ClusterOperation, bizerr.BizError) {
	rdb := i.ReadDB(ctx)

	var operations []*schema.ClusterOperation
	if err := buildClusterOperationGetter(
		rdb,
	).Where("`cluster_id` = ? AND `status` != ?", clusterID, globaldedicatedv1.ClusterOperationState_DONE).
		First(&operations).
		Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load cluster operations")
	}

	out := make(map[globaldedicatedv1.ClusterOperationType]*entity.ClusterOperation)
	for _, op := range operations {
		operation, err := clusterOperationModelToEn(op)
		if err != nil {
			return nil, err
		}
		out[operation.Type] = operation
	}

	return out, nil
}
