package spu

import (
	"context"
	"errors"

	"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/spu"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/schema"
	"github.com/tidbcloud/tidb-management-service/dedicated-global-service/infra/repoimpl/spu/cvt"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

type spuRepoImpl struct {
	provider db.Provider
}

func NewSpuRepo(provider db.Provider) spu.RepoSpu {
	return &spuRepoImpl{
		provider: provider,
	}
}

func (t *spuRepoImpl) Save(ctx context.Context, en *entity.Spu) (uint, bizerr.BizError) {
	model, err := cvt.SpuEnToModel(en)
	if err != nil {
		return 0, bizerr.WrapError(err, "convert tier %d failed", en.Id)
	}

	wdb := t.provider.WriteDB(ctx)
	// Omit created_at if it's an update operation
	if model.ID != 0 {
		wdb = wdb.Omit("created_at")
	}
	if err := wdb.Save(model).Error; err != nil {
		return 0, bizerr.WrapGormError(err, "create tier failed")
	}

	return model.ID, nil
}

func (t *spuRepoImpl) Query(ctx context.Context, filter *spu.QuerySpuFilter) ([]*entity.Spu, bizerr.BizError) {
	tx := t.provider.ReadDB(ctx).Model(&schema.ComponentTier{})

	if len(filter.ComponentTypes) > 0 {
		var componentTypes []string
		for _, componentType := range filter.ComponentTypes {
			if componentType != globaldedicatedv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED {
				componentTypes = append(componentTypes, componentType.String())
			}
		}
		tx = tx.Where("`component_tiers`.`component_type` in (?)", componentTypes)
	}

	if len(filter.Versions) > 0 {
		tx = tx.Where("`component_tiers`.`version` in (?)", filter.Versions)
	}

	if filter.Name != "" {
		tx = tx.Where("`component_tiers`.`name` = ?", filter.Name)
	}

	var componentTiers []*schema.ComponentTier
	if err := tx.Find(&componentTiers).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load all tiers failed")
	}

	out := make([]*entity.Spu, 0)
	for _, componentTier := range componentTiers {
		en, err := cvt.SpuModel2En(componentTier)
		if err != nil {
			return nil, bizerr.WrapError(err, "convert tier %d failed", componentTier.ID)
		}
		out = append(out, en)
	}

	return out, nil
}

func (t *spuRepoImpl) LoadByKeys(ctx context.Context, keys []string) (map[string]*entity.Spu, bizerr.BizError) {
	tx := t.provider.ReadDB(ctx).Model(&schema.ComponentTier{})

	if len(keys) > 0 {
		tx = tx.Where("`component_tiers`.`key` in (?)", keys)
	}

	var componentTiers []*schema.ComponentTier
	if err := tx.Find(&componentTiers).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load tiers by keys failed")
	}

	out := make(map[string]*entity.Spu, len(componentTiers))
	for _, componentTier := range componentTiers {
		en, err := cvt.SpuModel2En(componentTier)
		if err != nil {
			return nil, bizerr.WrapError(err, "convert tier %d failed", componentTier.ID)
		}
		out[en.Key] = en
	}

	return out, nil
}

func (t *spuRepoImpl) SaveSpuMappings(ctx context.Context, mappings []*entity.SpuMapping) ([]uint, bizerr.BizError) {
	models, err := cvt.SpuMappingsEnToModel(mappings)
	if err != nil {
		return []uint{}, bizerr.WrapError(err, "convert tier unit mappings failed")
	}
	wdb := t.provider.WriteDB(ctx)

	outs := make([]uint, 0)
	err_ := wdb.Transaction(func(tx *gorm.DB) error {
		for _, model := range models {
			existing := &schema.TierTierUnit{}
			err := tx.Where("component_tier_id = ?", model.ComponentTierId).First(existing).Error
			if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
				return bizerr.WrapGormError(err, "load tier unit mapping failed")
			}
			existing.ComponentTierId = model.ComponentTierId
			existing.TierUnitMapping = model.TierUnitMapping
			if err := wdb.Save(existing).Error; err != nil {
				return bizerr.WrapGormError(err, "create tier unit mapping failed")
			}
			outs = append(outs, model.ID)
		}
		return nil
	})
	if err_ != nil {
		return []uint{}, bizerr.WrapGormError(err_, "create tier unit mappings failed")
	}

	return outs, nil
}

func (t *spuRepoImpl) Get(ctx context.Context, id uint64) (*entity.Spu, bizerr.BizError) {
	tx := t.provider.ReadDB(ctx).Model(&schema.ComponentTier{})

	var componentTier *schema.ComponentTier
	if err := tx.Where("id = ?", id).First(&componentTier).Error; err != nil {
		return nil, bizerr.WrapGormError(err, "load all tiers failed")
	}

	en, err := cvt.SpuModel2En(componentTier)
	if err != nil {
		return nil, bizerr.WrapError(err, "convert tier %d failed", componentTier.ID)
	}

	return en, nil
}

func (t *spuRepoImpl) Delete(ctx context.Context, id uint64) bizerr.BizError {
	wdb := t.provider.WriteDB(ctx)

	err := wdb.Transaction(func(tx *gorm.DB) error {
		if err := wdb.Where("component_tier_id = ?", id).Delete(&schema.TierTierUnit{}).Error; err != nil {
			return bizerr.WrapGormError(err, "delete tier unit mapping failed")
		}
		if err := wdb.Where("id = ?", id).Delete(&schema.ComponentTier{}).Error; err != nil {
			return bizerr.WrapGormError(err, "delete tier %d failed", id)
		}
		return nil
	})

	if err != nil {
		return bizerr.WrapGormError(err, "delete tier %d failed", id)
	}

	return nil
}
