package promo

import (
	"context"
	"errors"

	"github.com/redis/go-redis/v9"
	"gorm.io/gorm"
)

type RuleWithPrice struct {
	Rule
	PromoPrice int64 `gorm:"column:promo_price"`
}

type Model interface {
	QueryEligibleRules(ctx context.Context, subscribeId int64, quantity int64) ([]*RuleWithPrice, error)
	InsertUsage(ctx context.Context, data *Usage, tx ...*gorm.DB) error
	InsertRule(ctx context.Context, data *Rule) error
	FindRule(ctx context.Context, id int64) (*Rule, error)
	UpdateRule(ctx context.Context, data *Rule) error
	DeleteRule(ctx context.Context, id int64) error
	QueryRuleList(ctx context.Context, page, size int, ruleType string, enabled *bool, search string) (int64, []*Rule, error)
	UpsertPrices(ctx context.Context, ruleId int64, items []*SubscribePromo) error
	FindPrice(ctx context.Context, id int64) (*SubscribePromo, error)
	DeletePrice(ctx context.Context, id int64) error
	QueryPriceList(ctx context.Context, params PriceFilter) (int64, []*SubscribePromo, error)
	QueryUsageList(ctx context.Context, params UsageFilter) (int64, []*Usage, error)
	Transaction(ctx context.Context, fn func(db *gorm.DB) error) error
}

type UsageFilter struct {
	Page        int
	Size        int
	RuleId      int64
	UserId      int64
	SubscribeId int64
	OrderNo     string
}

type PriceFilter struct {
	Page        int
	Size        int
	RuleId      int64
	SubscribeId int64
}

type defaultPromoModel struct {
	db *gorm.DB
}

func NewModel(db *gorm.DB, _ *redis.Client) Model {
	return &defaultPromoModel{db: db}
}

func (m *defaultPromoModel) QueryEligibleRules(ctx context.Context, subscribeId int64, quantity int64) ([]*RuleWithPrice, error) {
	var list []*RuleWithPrice
	err := m.db.WithContext(ctx).
		Table("promo_rule AS pr").
		Select("pr.*, sp.promo_price").
		Joins("JOIN subscribe_promo AS sp ON sp.promo_rule_id = pr.id").
		Where("sp.subscribe_id = ? AND sp.quantity = ? AND sp.promo_price > 0 AND pr.enabled = ?", subscribeId, quantity, true).
		Where("pr.deleted_at IS NULL").
		Order("pr.priority DESC").
		Order("pr.id ASC").
		Find(&list).Error
	return list, err
}

func (m *defaultPromoModel) InsertUsage(ctx context.Context, data *Usage, tx ...*gorm.DB) error {
	db := m.db.WithContext(ctx)
	if len(tx) > 0 {
		db = tx[0].WithContext(ctx)
	}
	return db.Model(&Usage{}).Create(data).Error
}

func (m *defaultPromoModel) InsertRule(ctx context.Context, data *Rule) error {
	return m.db.WithContext(ctx).Create(data).Error
}

func (m *defaultPromoModel) FindRule(ctx context.Context, id int64) (*Rule, error) {
	var resp Rule
	if err := m.db.WithContext(ctx).Model(&Rule{}).Where("id = ?", id).First(&resp).Error; err != nil {
		return nil, err
	}
	return &resp, nil
}

func (m *defaultPromoModel) UpdateRule(ctx context.Context, data *Rule) error {
	return m.db.WithContext(ctx).Model(&Rule{}).Where("id = ?", data.Id).Updates(map[string]interface{}{
		"name":       data.Name,
		"type":       data.Type,
		"params":     data.Params,
		"priority":   data.Priority,
		"enabled":    data.Enabled,
		"start_time": data.StartTime,
		"end_time":   data.EndTime,
	}).Error
}

func (m *defaultPromoModel) DeleteRule(ctx context.Context, id int64) error {
	return m.db.WithContext(ctx).Delete(&Rule{}, id).Error
}

func (m *defaultPromoModel) QueryRuleList(ctx context.Context, page, size int, ruleType string, enabled *bool, search string) (int64, []*Rule, error) {
	if page <= 0 {
		page = 1
	}
	if size <= 0 {
		size = 10
	}
	var total int64
	var list []*Rule
	db := m.db.WithContext(ctx).Model(&Rule{})
	if ruleType != "" {
		db = db.Where("type = ?", ruleType)
	}
	if enabled != nil {
		db = db.Where("enabled = ?", *enabled)
	}
	if search != "" {
		db = db.Where("name LIKE ?", "%"+search+"%")
	}
	if err := db.Count(&total).Error; err != nil {
		return 0, nil, err
	}
	err := db.Order("priority DESC").Order("id DESC").Limit(size).Offset((page - 1) * size).Find(&list).Error
	return total, list, err
}

func (m *defaultPromoModel) UpsertPrices(ctx context.Context, ruleId int64, items []*SubscribePromo) error {
	return m.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		for _, item := range items {
			if item == nil {
				continue
			}
			item.PromoRuleId = ruleId
			var existing SubscribePromo
			err := tx.Model(&SubscribePromo{}).
				Where("subscribe_id = ? AND quantity = ? AND promo_rule_id = ?", item.SubscribeId, item.Quantity, ruleId).
				First(&existing).Error
			if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
				return err
			}
			if errors.Is(err, gorm.ErrRecordNotFound) {
				if err := tx.Create(item).Error; err != nil {
					return err
				}
				continue
			}
			existing.Quantity = item.Quantity
			existing.PromoPrice = item.PromoPrice
			if err := tx.Save(&existing).Error; err != nil {
				return err
			}
		}
		return nil
	})
}

func (m *defaultPromoModel) FindPrice(ctx context.Context, id int64) (*SubscribePromo, error) {
	var resp SubscribePromo
	if err := m.db.WithContext(ctx).Model(&SubscribePromo{}).Where("id = ?", id).First(&resp).Error; err != nil {
		return nil, err
	}
	return &resp, nil
}

func (m *defaultPromoModel) DeletePrice(ctx context.Context, id int64) error {
	return m.db.WithContext(ctx).Delete(&SubscribePromo{}, id).Error
}

func (m *defaultPromoModel) QueryPriceList(ctx context.Context, params PriceFilter) (int64, []*SubscribePromo, error) {
	if params.Page <= 0 {
		params.Page = 1
	}
	if params.Size <= 0 {
		params.Size = 10
	}
	var total int64
	var list []*SubscribePromo
	db := m.db.WithContext(ctx).Model(&SubscribePromo{})
	if params.RuleId > 0 {
		db = db.Where("promo_rule_id = ?", params.RuleId)
	}
	if params.SubscribeId > 0 {
		db = db.Where("subscribe_id = ?", params.SubscribeId)
	}
	if err := db.Count(&total).Error; err != nil {
		return 0, nil, err
	}
	err := db.Order("id DESC").Limit(params.Size).Offset((params.Page - 1) * params.Size).Find(&list).Error
	return total, list, err
}

func (m *defaultPromoModel) QueryUsageList(ctx context.Context, params UsageFilter) (int64, []*Usage, error) {
	if params.Page <= 0 {
		params.Page = 1
	}
	if params.Size <= 0 {
		params.Size = 10
	}
	var total int64
	var list []*Usage
	db := m.db.WithContext(ctx).Model(&Usage{})
	if params.RuleId > 0 {
		db = db.Where("promo_rule_id = ?", params.RuleId)
	}
	if params.UserId > 0 {
		db = db.Where("user_id = ?", params.UserId)
	}
	if params.SubscribeId > 0 {
		db = db.Where("subscribe_id = ?", params.SubscribeId)
	}
	if params.OrderNo != "" {
		db = db.Where("order_no = ?", params.OrderNo)
	}
	if err := db.Count(&total).Error; err != nil {
		return 0, nil, err
	}
	err := db.Order("id DESC").Limit(params.Size).Offset((params.Page - 1) * params.Size).Find(&list).Error
	return total, list, err
}

func (m *defaultPromoModel) Transaction(ctx context.Context, fn func(db *gorm.DB) error) error {
	return m.db.WithContext(ctx).Transaction(fn)
}
