package repoimpl

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/go-kratos/kratos/v2/log"
	"github.com/redis/go-redis/v9"
	"gorm.io/gorm"
	"time"
	err2 "voucher/api/err"
	"voucher/internal/biz/bo"
	"voucher/internal/biz/do"
	"voucher/internal/biz/repo"
	"voucher/internal/biz/vo"
	"voucher/internal/data"
	"voucher/internal/data/model"
	"voucher/internal/pkg/lock"
)

// ProductRepoImpl .
type ProductRepoImpl struct {
	Base[model.Product, bo.ProductBo]
	db  *data.Db
	rdb *data.Rdb
}

// NewProductRepoImpl .
func NewProductRepoImpl(db *data.Db, rdb *data.Rdb) repo.ProductRepo {
	return &ProductRepoImpl{db: db, rdb: rdb}
}

func (r *ProductRepoImpl) FindWarningBudget(ctx context.Context, fun func(ctx context.Context, rows []*bo.ProductBo) error) error {
	var results = make([]*model.Product, 0)

	nowTime := time.Now().Format(time.DateTime)

	result := r.db.DB(ctx).
		Where("start_time < ?", nowTime).
		Where("end_time > ?", nowTime).
		Where("warning_budget > 0").
		Where("warning_person IS NOT NULL").
		FindInBatches(&results, 5, func(tx *gorm.DB, batch int) error {
			return fun(ctx, r.ToBos(results))
		})

	if result.Error != nil {
		return result.Error
	}

	return nil
}

func (r *ProductRepoImpl) UpdateByWxResp(ctx context.Context, id int32, req *do.WxResp) error {

	now := time.Now()

	u := model.Product{
		Amount:          req.Amount,
		AllBudget:       req.AllBudget,
		AvailableBudget: req.AvailableBudget,
		UpdateTime:      &now,
	}

	if req.StartTime != nil {
		u.StartTime = req.StartTime
	}
	if req.EndTime != nil {
		u.EndTime = req.EndTime
	}

	tx := r.db.DB(ctx).Where(model.Product{ID: id}).Updates(u)

	if tx.Error != nil {
		return fmt.Errorf("db fail %w", tx.Error)
	}

	return nil
}

func (r *ProductRepoImpl) GetById(ctx context.Context, id int32) (*bo.ProductBo, error) {

	var item *model.Product

	tx := r.db.DB(ctx).Model(model.Product{}).Where(model.Product{ID: id}).First(&item)

	if tx.Error != nil {
		return nil, tx.Error
	}

	if tx.RowsAffected == 0 {
		return nil, gorm.ErrRecordNotFound
	}

	return r.ToBo(item), nil
}

func (r *ProductRepoImpl) GetByBatchNo(ctx context.Context, batchNo string) (*bo.ProductBo, error) {

	var item *model.Product

	db := r.db.DB(ctx).Model(model.Product{})
	tx := db.Where(model.Product{BatchNo: batchNo}).First(&item)

	if tx.Error != nil {
		return nil, tx.Error
	}

	if tx.RowsAffected == 0 {
		return nil, gorm.ErrRecordNotFound
	}

	return r.ToBo(item), nil
}

func (r *ProductRepoImpl) GetByMchStockId(ctx context.Context, mchId, stockId string) (*bo.ProductBo, error) {

	var item *model.Product

	tx := r.db.DB(ctx).Where(model.Product{MchId: mchId, BatchNo: stockId}).First(&item)

	if tx.Error != nil {
		return nil, tx.Error
	}

	if tx.RowsAffected == 0 {
		return nil, gorm.ErrRecordNotFound
	}

	return r.ToBo(item), nil
}

func (r *ProductRepoImpl) DelCacheByProductNo(ctx context.Context, productNo string) error {

	c := vo.ProductQueryKey.BuildCache([]string{productNo})

	_, _ = r.rdb.Rdb.Del(ctx, c.Key).Result()

	return nil
}

func (r *ProductRepoImpl) GetByProductNo(ctx context.Context, productNo string) (*bo.ProductBo, error) {

	c := vo.ProductQueryKey.BuildCache([]string{productNo})

	cacheValue, err := r.rdb.Rdb.Get(context.Background(), c.Key).Result()

	if err != nil && err != redis.Nil {
		return nil, fmt.Errorf(fmt.Sprintf("获取商品缓存异常,%s:%v", c.Key, err))
	}

	var item *model.Product

	if len(cacheValue) > 0 {
		if err = json.Unmarshal([]byte(cacheValue), &item); err != nil {
			return nil, err
		}
		return r.ToBo(item), nil
	}

	cl := vo.ProductQueryLockKey.BuildCache([]string{productNo})

	err = lock.NewMutex(r.rdb.Rdb, cl.TTL).Lock(ctx, cl.Key, func(ctx context.Context) error {

		cacheValue, err = r.rdb.Rdb.Get(ctx, c.Key).Result()

		if err != nil && err != redis.Nil {
			return fmt.Errorf(fmt.Sprintf("二次获取商品缓存异常,%s:%v", c.Key, err))
		}

		if len(cacheValue) > 0 {
			return json.Unmarshal([]byte(cacheValue), &item)
		}

		item, err = r.getByProductNo(ctx, item, productNo)
		if err != nil {
			return err
		}

		b, err3 := json.Marshal(item)
		if err3 != nil {
			return err3
		}

		return r.rdb.Rdb.Set(ctx, c.Key, string(b), c.TTL).Err()
	})

	if err != nil {
		return nil, err
	}

	return r.ToBo(item), err
}

func (r *ProductRepoImpl) getByProductNo(ctx context.Context, item *model.Product, productNo string) (*model.Product, error) {

	db := r.db.DB(ctx).Model(model.Product{})
	tx := db.Where(model.Product{ProductNo: productNo}).First(&item)

	if tx.Error != nil {

		if errors.Is(tx.Error, gorm.ErrRecordNotFound) {
			return nil, err2.ErrorDbNotFound("商品数据不存在")
		}

		sqlDB, _ := db.DB()
		log.Warnf("product当前打开连接数:%d,空闲连接数:%d", sqlDB.Stats().OpenConnections, sqlDB.Stats().Idle)

		return nil, fmt.Errorf("product db fail %w", tx.Error)
	}

	if tx.RowsAffected == 0 {
		return nil, err2.ErrorDbNotFound("商品数据不存在")
	}

	return item, nil
}
