package exchange

import (
	"context"
	"time"

	"github.com/break/junhong_cmp_fiber/internal/model"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	"github.com/break/junhong_cmp_fiber/pkg/middleware"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

func (s *Service) executeMigrationWithTx(ctx context.Context, tx *gorm.DB, order *model.ExchangeOrder, oldAsset, newAsset *resolvedExchangeAsset) (int64, error) {
	migrationBalance, err := s.transferWalletBalanceWithTx(ctx, tx, order, oldAsset, newAsset)
	if err != nil {
		return 0, errors.Wrap(errors.CodeExchangeMigrationFailed, err, "执行钱包迁移失败")
	}
	if err = s.migratePackageUsageWithTx(ctx, tx, oldAsset, newAsset); err != nil {
		return 0, errors.Wrap(errors.CodeExchangeMigrationFailed, err, "迁移套餐使用记录失败")
	}
	if err = s.copyAccumulatedFieldsWithTx(tx, oldAsset, newAsset); err != nil {
		return 0, errors.Wrap(errors.CodeExchangeMigrationFailed, err, "复制累计充值字段失败")
	}
	if err = s.copyResourceTagsWithTx(ctx, tx, oldAsset, newAsset); err != nil {
		return 0, errors.Wrap(errors.CodeExchangeMigrationFailed, err, "复制资产标签失败")
	}
	return migrationBalance, nil
}

func (s *Service) transferWalletBalanceWithTx(ctx context.Context, tx *gorm.DB, order *model.ExchangeOrder, oldAsset, newAsset *resolvedExchangeAsset) (int64, error) {
	var oldWallet model.AssetWallet
	if err := tx.WithContext(ctx).Where("resource_type = ? AND resource_id = ?", oldAsset.AssetType, oldAsset.AssetID).First(&oldWallet).Error; err != nil {
		if err != gorm.ErrRecordNotFound {
			return 0, errors.Wrap(errors.CodeDatabaseError, err, "查询旧资产钱包失败")
		}
	}
	if oldWallet.ID > 0 && oldWallet.FrozenBalance > 0 {
		return 0, errors.New(errors.CodeExchangeMigrationFailed, "旧资产钱包存在冻结余额，请先处理冻结业务")
	}

	var newWallet model.AssetWallet
	if err := tx.WithContext(ctx).Where("resource_type = ? AND resource_id = ?", newAsset.AssetType, newAsset.AssetID).First(&newWallet).Error; err != nil {
		if err != gorm.ErrRecordNotFound {
			return 0, errors.Wrap(errors.CodeDatabaseError, err, "查询新资产钱包失败")
		}

		shopTag := uint(0)
		if newAsset.ShopID != nil {
			shopTag = *newAsset.ShopID
		}
		newWallet = model.AssetWallet{ResourceType: newAsset.AssetType, ResourceID: newAsset.AssetID, Balance: 0, FrozenBalance: 0, Currency: "CNY", Status: constants.AssetWalletStatusNormal, Version: 0, ShopIDTag: shopTag}
		if err = tx.WithContext(ctx).Create(&newWallet).Error; err != nil {
			return 0, errors.Wrap(errors.CodeDatabaseError, err, "创建新资产钱包失败")
		}
	}

	migrationBalance := oldWallet.Balance
	if migrationBalance <= 0 {
		return 0, nil
	}

	beforeBalance := newWallet.Balance
	if err := tx.WithContext(ctx).Model(&model.AssetWallet{}).Where("id = ?", oldWallet.ID).Updates(map[string]any{"balance": 0, "updated_at": time.Now()}).Error; err != nil {
		return 0, errors.Wrap(errors.CodeDatabaseError, err, "清空旧资产钱包余额失败")
	}

	if err := tx.WithContext(ctx).Model(&model.AssetWallet{}).Where("id = ?", newWallet.ID).Updates(map[string]any{"balance": gorm.Expr("balance + ?", migrationBalance), "updated_at": time.Now()}).Error; err != nil {
		return 0, errors.Wrap(errors.CodeDatabaseError, err, "增加新资产钱包余额失败")
	}

	refType := constants.ReferenceTypeExchange
	remark := "换货余额迁移"
	if err := tx.WithContext(ctx).Create(&model.AssetWalletTransaction{
		AssetWalletID:   newWallet.ID,
		ResourceType:    newAsset.AssetType,
		ResourceID:      newAsset.AssetID,
		UserID:          middleware.GetUserIDFromContext(ctx),
		TransactionType: constants.AssetTransactionTypeExchange,
		Amount:          migrationBalance,
		BalanceBefore:   beforeBalance,
		BalanceAfter:    beforeBalance + migrationBalance,
		Status:          constants.TransactionStatusSuccess,
		ReferenceType:   &refType,
		ReferenceNo:     &order.ExchangeNo,
		Remark:          &remark,
		Creator:         middleware.GetUserIDFromContext(ctx),
		ShopIDTag:       newWallet.ShopIDTag,
		EnterpriseIDTag: newWallet.EnterpriseIDTag,
	}).Error; err != nil {
		return 0, errors.Wrap(errors.CodeDatabaseError, err, "写入迁移钱包流水失败")
	}

	return migrationBalance, nil
}

func (s *Service) migratePackageUsageWithTx(ctx context.Context, tx *gorm.DB, oldAsset, newAsset *resolvedExchangeAsset) error {
	query := tx.WithContext(ctx).Model(&model.PackageUsage{}).Where("status IN ?", []int{constants.PackageUsageStatusPending, constants.PackageUsageStatusActive, constants.PackageUsageStatusDepleted})
	if oldAsset.AssetType == constants.ExchangeAssetTypeIotCard {
		query = query.Where("iot_card_id = ?", oldAsset.AssetID)
	} else {
		query = query.Where("device_id = ?", oldAsset.AssetID)
	}

	var usageIDs []uint
	if err := query.Pluck("id", &usageIDs).Error; err != nil {
		return errors.Wrap(errors.CodeDatabaseError, err, "查询套餐使用记录失败")
	}

	if len(usageIDs) == 0 {
		return nil
	}

	updates := map[string]any{"updated_at": time.Now()}
	if oldAsset.AssetType == constants.ExchangeAssetTypeIotCard {
		updates["iot_card_id"] = newAsset.AssetID
	} else {
		updates["device_id"] = newAsset.AssetID
	}

	if err := tx.WithContext(ctx).Model(&model.PackageUsage{}).Where("id IN ?", usageIDs).Updates(updates).Error; err != nil {
		return errors.Wrap(errors.CodeDatabaseError, err, "迁移套餐使用记录失败")
	}

	if err := tx.WithContext(ctx).Model(&model.PackageUsageDailyRecord{}).Where("package_usage_id IN ?", usageIDs).Update("updated_at", gorm.Expr("updated_at")).Error; err != nil {
		return errors.Wrap(errors.CodeDatabaseError, err, "迁移套餐日记录失败")
	}

	return nil
}

func (s *Service) copyAccumulatedFieldsWithTx(tx *gorm.DB, oldAsset, newAsset *resolvedExchangeAsset) error {
	if oldAsset.AssetType == constants.ExchangeAssetTypeIotCard {
		if oldAsset.Card == nil {
			return errors.New(errors.CodeAssetNotFound)
		}
		if err := tx.Model(&model.IotCard{}).Where("id = ?", newAsset.AssetID).Updates(map[string]any{
			"accumulated_recharge_by_series":     oldAsset.Card.AccumulatedRechargeBySeriesJSON,
			"first_recharge_triggered_by_series": oldAsset.Card.FirstRechargeTriggeredBySeriesJSON,
			"updated_at":                         time.Now(),
		}).Error; err != nil {
			return errors.Wrap(errors.CodeDatabaseError, err, "复制旧卡累计字段失败")
		}
		return nil
	}

	if oldAsset.Device == nil {
		return errors.New(errors.CodeAssetNotFound)
	}
	if err := tx.Model(&model.Device{}).Where("id = ?", newAsset.AssetID).Updates(map[string]any{
		"accumulated_recharge_by_series":     oldAsset.Device.AccumulatedRechargeBySeriesJSON,
		"first_recharge_triggered_by_series": oldAsset.Device.FirstRechargeTriggeredBySeriesJSON,
		"updated_at":                         time.Now(),
	}).Error; err != nil {
		return errors.Wrap(errors.CodeDatabaseError, err, "复制旧设备累计字段失败")
	}
	return nil
}

func (s *Service) copyResourceTagsWithTx(ctx context.Context, tx *gorm.DB, oldAsset, newAsset *resolvedExchangeAsset) error {
	var tags []*model.ResourceTag
	if err := tx.WithContext(ctx).Where("resource_type = ? AND resource_id = ?", oldAsset.AssetType, oldAsset.AssetID).Find(&tags).Error; err != nil {
		return errors.Wrap(errors.CodeDatabaseError, err, "查询资源标签失败")
	}
	var creator = middleware.GetUserIDFromContext(ctx)
	for _, item := range tags {
		if item == nil {
			continue
		}
		record := &model.ResourceTag{
			ResourceType: newAsset.AssetType,
			ResourceID:   newAsset.AssetID,
			TagID:        item.TagID,
			EnterpriseID: item.EnterpriseID,
			ShopID:       item.ShopID,
			BaseModel:    model.BaseModel{Creator: creator, Updater: creator},
		}
		if err := tx.WithContext(ctx).Clauses(clause.OnConflict{Columns: []clause.Column{{Name: "resource_type"}, {Name: "resource_id"}, {Name: "tag_id"}}, DoNothing: true}).Create(record).Error; err != nil {
			return errors.Wrap(errors.CodeDatabaseError, err, "复制资源标签失败")
		}
	}
	return nil
}
