package repo_imp

import (
	"context"

	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/db"
	"github.com/tidbcloud/pkgv2/tracing"
	"github.com/tidbcloud/serverless-service/internal/domain/entity"
	"github.com/tidbcloud/serverless-service/internal/domain/repo"
	"gorm.io/gorm"
)

type importRepoImpl struct {
	dbProvider db.Provider
}

func NewImportRepo(dbProvider db.Provider) repo.ImportRepo {
	return &importRepoImpl{dbProvider}
}

func (r *importRepoImpl) GetImportByID(
	ctx context.Context,
	clusterID string,
	id string,
) (i *entity.Import, err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "importRepo.GetImportByID")
	defer func() { tracing.EndSpan(span, err) }()

	rdb := r.dbProvider.ReadDB(ctx)
	i = &entity.Import{}
	if err := rdb.Model(&entity.Import{}).Where("id = ? AND cluster_id = ?", id, clusterID).First(i).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to get import, id: %s", id)
	}

	return i, nil
}

func (r *importRepoImpl) GetImportByIDCompat(
	ctx context.Context,
	clusterID string,
	idCompat uint64,
) (i *entity.Import, err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "importRepo.GetImportByIDCompat")
	defer func() { tracing.EndSpan(span, err) }()

	rdb := r.dbProvider.ReadDB(ctx)
	i = &entity.Import{}
	if err := rdb.Model(&entity.Import{}).Where("id_compat = ? AND cluster_id = ?", idCompat, clusterID).First(i).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to get import, id_compat: %d", idCompat)
	}

	return i, nil
}

func (r *importRepoImpl) Update(ctx context.Context, record *entity.Import, columns ...string) (err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "importRepo.Update")
	defer func() { tracing.EndSpan(span, err) }()

	if record == nil || record.ID == "" {
		return nil
	}

	wdb := r.dbProvider.WriteDB(ctx)
	if len(columns) > 0 {
		wdb = wdb.Select(columns)
	}
	result := wdb.Updates(record)
	if result.Error != nil {
		return bizerr.WrapGormError(result.Error, "failed to Update Import: %s", record.ID)
	}

	return nil
}

func (r *importRepoImpl) ListImportsByFilter(
	ctx context.Context,
	filter *entity.ImportFilter,
) (imports []*entity.Import, err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "importRepo.ListImportsByFilter")
	defer func() { tracing.EndSpan(span, err) }()
	rdb := r.dbProvider.ReadDB(ctx)
	rdb, err = buildImportFilter(filter, rdb)
	if err != nil {
		return nil, err
	}

	if err := rdb.Model(&entity.Import{}).Find(&imports).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to list imports by filter")
	}

	return imports, nil
}

func buildImportFilter(filter *entity.ImportFilter, db *gorm.DB) (*gorm.DB, bizerr.BizError) {
	if filter == nil {
		return nil, bizerr.New(bizerr.ErrCodeDB, "filter is required")
	}
	if filter.ClusterID != "" {
		db = db.Where("cluster_id = ?", filter.ClusterID)
	}
	if filter.Offset != nil {
		db = db.Offset(int(*filter.Offset))
	}
	if filter.PageSize != nil {
		db = db.Limit(int(*filter.PageSize))
	}
	if filter.Statuses != nil {
		db = db.Where("status in ?", filter.Statuses)
	}
	if filter.CreatedAtAfter != nil {
		db = db.Where("created_at >= ?", filter.CreatedAtAfter)
	}
	if filter.CompletedAtAfter != nil {
		db = db.Where("completed_at >= ?", filter.CompletedAtAfter)
	}
	if filter.CleanUp != nil {
		db = db.Where("clean_up = ?", filter.CleanUp)
	}
	if filter.OrderBy != "" {
		db = db.Order(filter.OrderBy)
	}

	return db, nil
}

func (r *importRepoImpl) CountImports(ctx context.Context, filter *entity.ImportFilter) (_ int64, err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "importRepo.CountImports")
	defer func() { tracing.EndSpan(span, err) }()

	if filter == nil {
		return 0, bizerr.New(bizerr.ErrCodeDB, "filter is required")
	}
	var count int64
	rdb := r.dbProvider.ReadDB(ctx)
	rdb, err = buildImportFilter(filter, rdb)
	if err != nil {
		return 0, err
	}

	if err := rdb.Model(&entity.Import{}).Count(&count).Error; err != nil {
		return 0, bizerr.WrapGormError(err, "failed to count imports")
	}
	return count, nil
}

func (r *importRepoImpl) Create(ctx context.Context, i *entity.Import) (err bizerr.BizError) {
	ctx, span := Tracer.Start(ctx, "importRepo.Create")
	defer func() { tracing.EndSpan(span, err) }()

	wdb := r.dbProvider.WriteDB(ctx)
	if err := wdb.Create(i).Error; err != nil {
		return bizerr.WrapGormError(err, "failed to create import")
	}

	return nil
}
