package repository

import (
	"context"

	"github.com/tidbcloud/pkgv2/db"

	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/repository"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/infra/repository/schema"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

type FsRegionRepoImpl struct {
	dbProvider db.Provider
}

func NewFsRegionRepo(dbProvider db.Provider) repository.FsRegionRepository {
	return &FsRegionRepoImpl{dbProvider: dbProvider}
}

func (r *FsRegionRepoImpl) List(ctx context.Context, cloudProvider, status string) ([]*entity.FsRegion, error) {
	ctx, span := tracer.Start(ctx, "FsRegionRepo.List")
	defer span.End()

	rdb := r.dbProvider.ReadDB(ctx)
	query := rdb.Model(&schema.FsRegion{})
	if cloudProvider != "" {
		query = query.Where("cloud_provider = ?", cloudProvider)
	}
	if status != "" {
		query = query.Where("status = ?", status)
	}

	var models []schema.FsRegion
	if err := query.Find(&models).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to list fs regions")
	}

	regions := make([]*entity.FsRegion, 0, len(models))
	for i := range models {
		regions = append(regions, fsRegionToEntity(&models[i]))
	}
	return regions, nil
}

func (r *FsRegionRepoImpl) Get(ctx context.Context, cloudProvider, region string) (*entity.FsRegion, error) {
	ctx, span := tracer.Start(ctx, "FsRegionRepo.Get")
	defer span.End()

	if cloudProvider == "" || region == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "cloud_provider and region are required")
	}

	rdb := r.dbProvider.ReadDB(ctx)
	var m schema.FsRegion
	if err := rdb.Where("cloud_provider = ? AND region = ?", cloudProvider, region).First(&m).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to get fs region")
	}
	return fsRegionToEntity(&m), nil
}

func (r *FsRegionRepoImpl) Create(ctx context.Context, region *entity.FsRegion) (*entity.FsRegion, error) {
	ctx, span := tracer.Start(ctx, "FsRegionRepo.Create")
	defer span.End()

	if err := validateFsRegion(region); err != nil {
		return nil, err
	}

	wdb := r.dbProvider.WriteDB(ctx)
	m := &schema.FsRegion{
		CloudProvider:  region.CloudProvider,
		Region:         region.Region,
		DisplayName:    region.DisplayName,
		ServiceAddress: region.ServiceAddress,
		Status:         region.Status,
	}
	if err := wdb.Create(m).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "failed to create fs region")
	}
	return fsRegionToEntity(m), nil
}

func (r *FsRegionRepoImpl) Update(ctx context.Context, region *entity.FsRegion) (*entity.FsRegion, error) {
	ctx, span := tracer.Start(ctx, "FsRegionRepo.Update")
	defer span.End()

	if err := validateFsRegion(region); err != nil {
		return nil, err
	}

	// Only update non-empty fields so that a PATCH-style update omitting a
	// field does not wipe the stored value.
	updates := map[string]any{}
	if region.DisplayName != "" {
		updates["display_name"] = region.DisplayName
	}
	if region.ServiceAddress != "" {
		updates["service_address"] = region.ServiceAddress
	}
	if region.Status != "" {
		updates["status"] = region.Status
	}
	if len(updates) == 0 {
		return nil, bizerr.New(bizerr.ErrCodeParams, "no fields to update")
	}

	wdb := r.dbProvider.WriteDB(ctx)
	result := wdb.Model(&schema.FsRegion{}).
		Where("cloud_provider = ? AND region = ?", region.CloudProvider, region.Region).
		Updates(updates)
	if result.Error != nil {
		return nil, bizerr.WrapGormError(result.Error, "failed to update fs region")
	}
	if result.RowsAffected == 0 {
		return nil, bizerr.New(bizerr.ErrCodeRecordNotFound, "fs region %s-%s not found", region.CloudProvider, region.Region)
	}

	// Return the refreshed record so the response reflects persisted values.
	return r.Get(ctx, region.CloudProvider, region.Region)
}

func validateFsRegion(region *entity.FsRegion) error {
	if region == nil {
		return bizerr.New(bizerr.ErrCodeParams, "region is nil")
	}
	if region.CloudProvider == "" || region.Region == "" {
		return bizerr.New(bizerr.ErrCodeParams, "cloud_provider and region are required")
	}
	return nil
}

func fsRegionToEntity(m *schema.FsRegion) *entity.FsRegion {
	return &entity.FsRegion{
		CloudProvider:  m.CloudProvider,
		Region:         m.Region,
		DisplayName:    m.DisplayName,
		ServiceAddress: m.ServiceAddress,
		Status:         m.Status,
	}
}
