package br

import (
	"context"
	"time"

	"github.com/tidbcloud/pkgv2/db"
	"go.opentelemetry.io/otel"
	"gorm.io/gorm"

	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/br"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/common"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

var restoreTracer = otel.Tracer("restore_repo_impl")

type restoreRepoImpl struct {
	provider db.Provider
}

func NewRestoreRepo(provider db.Provider) br.RestoreRepo {
	return &restoreRepoImpl{
		provider: provider,
	}
}

func (r *restoreRepoImpl) Create(ctx context.Context, restore *entity.Restore) bizerr.BizError {
	ctx, span := restoreTracer.Start(ctx, "restoreRepo.Create")
	defer span.End()

	if restore == nil {
		return bizerr.New(bizerr.ErrCodeUnknown, "restore entity is nil")
	}

	wdb := r.provider.WriteDB(ctx)
	schemaRestore := restore.Restore
	now := time.Now().UTC()
	schemaRestore.CreatedAt = now
	schemaRestore.UpdatedAt = now

	if err := wdb.Create(schemaRestore).Error; err != nil {
		return bizerr.WrapGormError(err, "failed to create restore")
	}

	// Update the entity with the generated ID
	restore.ID = schemaRestore.ID
	return nil
}

func (r *restoreRepoImpl) Save(ctx context.Context, restore *entity.Restore) bizerr.BizError {
	ctx, span := restoreTracer.Start(ctx, "restoreRepo.Save")
	defer span.End()

	if restore == nil {
		return bizerr.New(bizerr.ErrCodeUnknown, "restore entity is nil")
	}

	wdb := r.provider.WriteDB(ctx)

	schemaRestore := restore.Restore
	schemaRestore.UpdatedAt = time.Now().UTC()

	if berr := common.SaveWithVersioning(wdb, schemaRestore, nil); berr != nil {
		return berr.Chain("failed to save restore")
	}

	return nil
}

func (r *restoreRepoImpl) Delete(ctx context.Context, clusterId uint64) bizerr.BizError {
	ctx, span := restoreTracer.Start(ctx, "restoreRepo.Delete")
	defer span.End()

	wdb := r.provider.WriteDB(ctx)

	// First, get the existing restore to ensure it exists and is not already deleted
	var schemaRestore schema.Restore
	if err := wdb.Where("target_cluster_id = ? AND deleted_at IS NULL", clusterId).First(&schemaRestore).Error; err != nil {
		return bizerr.WrapGormError(err, "failed to get restore for deletion")
	}

	// Perform soft delete by updating DeletedAt field
	now := wdb.NowFunc().UTC()
	schemaRestore.DeletedAt = gorm.DeletedAt{Time: now, Valid: true}
	schemaRestore.UpdatedAt = now

	if berr := common.SaveWithVersioning(wdb, &schemaRestore, nil); berr != nil {
		return berr.Chain("failed to soft delete restore")
	}

	return nil
}

func (r *restoreRepoImpl) Get(ctx context.Context, clusterId uint64) (*entity.Restore, bizerr.BizError) {
	ctx, span := restoreTracer.Start(ctx, "restoreRepo.Get")
	defer span.End()

	// Validate cluster ID is not zero
	if clusterId == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cluster ID cannot be zero")
	}

	rdb := r.provider.ReadDB(ctx)
	var schemaRestore schema.Restore
	if err := rdb.Unscoped().Where("target_cluster_id = ?", clusterId).First(&schemaRestore).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to get restore")
	}

	return &entity.Restore{
		Restore: &schemaRestore,
	}, nil
}

func (r *restoreRepoImpl) HasActiveByTargetClusterIDs(
	ctx context.Context,
	clusterIDs []uint64,
) (bool, bizerr.BizError) {
	if len(clusterIDs) == 0 {
		return false, nil
	}

	var count int64
	if err := r.provider.ReadDB(ctx).
		Model(&schema.Restore{}).
		Where("target_cluster_id IN ? AND state IN ?", clusterIDs, []string{"pending", "running"}).
		Limit(1).
		Count(&count).Error; err != nil {
		return false, bizerr.WrapGormError(err, "failed to query active restores")
	}
	return count > 0, nil
}
