package storage

import (
	"crypto/sha256"
	"fmt"
	"strings"
	"sync"
	"time"

	"github.com/google/uuid"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"

	"mengyastore-backend/internal/database"
	"mengyastore-backend/internal/models"
)

const defaultCoverURL = "https://img.shumengya.top/i/2026/01/04/695a55058c37f.png"
const viewCooldown = 6 * time.Hour
const maxScreenshotURLs = 6
const maxPaymentQrURLs = 6

type ProductStore struct {
	db          *gorm.DB
	mu          sync.Mutex
	recentViews map[string]time.Time
}

func NewProductStore(db *gorm.DB) (*ProductStore, error) {
	return &ProductStore{
		db:          db,
		recentViews: make(map[string]time.Time),
	}, nil
}

func effectiveFulfillment(t string) string {
	switch strings.ToLower(strings.TrimSpace(t)) {
	case "fixed":
		return "fixed"
	default:
		return "card"
	}
}

// rowToModel 将数据库行（含卡密）转换为业务模型。固定内容类商品 Quantity 为 -1 表示不限库存。
func rowToModel(row database.ProductRow, codes []string) models.Product {
	ft := effectiveFulfillment(row.FulfillmentType)
	qty := len(codes)
	if ft == "fixed" {
		qty = -1
	}
	return models.Product{
		ID:              row.ID,
		Name:            row.Name,
		Price:           row.Price,
		DiscountPrice:   row.DiscountPrice,
		Tags:            row.Tags,
		CoverURL:        row.CoverURL,
		ScreenshotURLs:  row.ScreenshotURLs,
		PaymentQrURLs:   []string(row.PaymentQrURLs),
		VerificationURL: row.VerificationURL,
		Description:     row.Description,
		Active:          row.Active,
		RequireLogin:    row.RequireLogin,
		MaxPerAccount:   row.MaxPerAccount,
		TotalSold:       row.TotalSold,
		ViewCount:       row.ViewCount,
		DeliveryMode:    row.DeliveryMode,
		FulfillmentType: ft,
		FixedContent:    row.FixedContent,
		ShowNote:        row.ShowNote,
		ShowContact:     row.ShowContact,
		Codes:           codes,
		Quantity:        qty,
		CreatedAt:       row.CreatedAt,
	}
}

func (s *ProductStore) loadCodes(productID string) ([]string, error) {
	return s.loadCodesTx(s.db, productID)
}

func (s *ProductStore) loadCodesTx(tx *gorm.DB, productID string) ([]string, error) {
	var rows []database.ProductCodeRow
	if err := tx.Where("product_id = ?", productID).Find(&rows).Error; err != nil {
		return nil, err
	}
	codes := make([]string, len(rows))
	for i, r := range rows {
		codes[i] = r.Code
	}
	return codes, nil
}

// Transaction 在同一数据库连接上开启事务并执行 fn。
func (s *ProductStore) Transaction(fn func(tx *gorm.DB) error) error {
	return s.db.Transaction(fn)
}

// GetByIDForUpdate 在事务内以 FOR UPDATE 锁定商品行（须在事务中调用）。
func (s *ProductStore) GetByIDForUpdate(tx *gorm.DB, id string) (models.Product, error) {
	var row database.ProductRow
	if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).First(&row, "id = ?", id).Error; err != nil {
		return models.Product{}, fmt.Errorf("product not found")
	}
	codes, err := s.loadCodesTx(tx, id)
	if err != nil {
		return models.Product{}, err
	}
	return rowToModel(row, codes), nil
}

// PrependReservedCodes 把曾预留在订单里的卡密按原顺序放回该商品可用库存队列前端（FIFO 出库顺序）。
func (s *ProductStore) PrependReservedCodes(productID string, codes []string) error {
	codes = sanitizeCodes(codes)
	if len(codes) == 0 {
		return nil
	}
	existing, err := s.loadCodes(productID)
	if err != nil {
		return err
	}
	merged := make([]string, 0, len(codes)+len(existing))
	merged = append(merged, codes...)
	merged = append(merged, existing...)
	return s.replaceCodes(productID, merged)
}

func (s *ProductStore) replaceCodes(productID string, codes []string) error {
	return s.replaceCodesTx(s.db, productID, codes)
}

func (s *ProductStore) replaceCodesTx(tx *gorm.DB, productID string, codes []string) error {
	if err := tx.Where("product_id = ?", productID).Delete(&database.ProductCodeRow{}).Error; err != nil {
		return err
	}
	if len(codes) == 0 {
		return nil
	}
	rows := make([]database.ProductCodeRow, 0, len(codes))
	for _, code := range codes {
		rows = append(rows, database.ProductCodeRow{ProductID: productID, Code: code})
	}
	return tx.CreateInBatches(rows, 100).Error
}

func (s *ProductStore) ListAll() ([]models.Product, error) {
	var rows []database.ProductRow
	if err := s.db.Order("created_at DESC").Find(&rows).Error; err != nil {
		return nil, err
	}
	products := make([]models.Product, 0, len(rows))
	for _, row := range rows {
		codes, _ := s.loadCodes(row.ID)
		products = append(products, rowToModel(row, codes))
	}
	return products, nil
}

func (s *ProductStore) ListActive() ([]models.Product, error) {
	var rows []database.ProductRow
	if err := s.db.Where("active = ?", true).Order("created_at DESC").Find(&rows).Error; err != nil {
		return nil, err
	}
	products := make([]models.Product, 0, len(rows))
	for _, row := range rows {
		ft := effectiveFulfillment(row.FulfillmentType)
		p := rowToModel(row, nil)
		p.Codes = nil
		if ft == "fixed" {
			p.Quantity = -1
			p.FixedContent = ""
		} else {
			var count int64
			s.db.Model(&database.ProductCodeRow{}).Where("product_id = ?", row.ID).Count(&count)
			p.Quantity = int(count)
		}
		products = append(products, p)
	}
	return products, nil
}

func (s *ProductStore) GetByID(id string) (models.Product, error) {
	var row database.ProductRow
	if err := s.db.First(&row, "id = ?", id).Error; err != nil {
		return models.Product{}, fmt.Errorf("product not found")
	}
	codes, _ := s.loadCodes(id)
	return rowToModel(row, codes), nil
}

func (s *ProductStore) Create(p models.Product) (models.Product, error) {
	p = normalizeProduct(p)
	p.ID = uuid.NewString()
	now := time.Now()
	p.CreatedAt = now

	row := database.ProductRow{
		ID:              p.ID,
		Name:            p.Name,
		Price:           p.Price,
		DiscountPrice:   p.DiscountPrice,
		Tags:            database.StringSlice(p.Tags),
		CoverURL:        p.CoverURL,
		ScreenshotURLs:  database.StringSlice(p.ScreenshotURLs),
		PaymentQrURLs:   database.StringSlice(p.PaymentQrURLs),
		VerificationURL: p.VerificationURL,
		Description:     p.Description,
		Active:          p.Active,
		RequireLogin:    p.RequireLogin,
		MaxPerAccount:   p.MaxPerAccount,
		TotalSold:       p.TotalSold,
		ViewCount:       p.ViewCount,
		DeliveryMode:    p.DeliveryMode,
		FulfillmentType: p.FulfillmentType,
		FixedContent:    p.FixedContent,
		ShowNote:        p.ShowNote,
		ShowContact:     p.ShowContact,
		CreatedAt:       now,
	}
	if err := s.db.Create(&row).Error; err != nil {
		return models.Product{}, err
	}
	if err := s.replaceCodes(p.ID, p.Codes); err != nil {
		return models.Product{}, err
	}
	var createdRow database.ProductRow
	s.db.First(&createdRow, "id = ?", p.ID)
	codes, _ := s.loadCodes(p.ID)
	return rowToModel(createdRow, codes), nil
}

func (s *ProductStore) Update(id string, patch models.Product) (models.Product, error) {
	var row database.ProductRow
	if err := s.db.First(&row, "id = ?", id).Error; err != nil {
		return models.Product{}, fmt.Errorf("product not found")
	}
	normalized := normalizeProduct(patch)

	if err := s.db.Model(&row).Updates(map[string]interface{}{
		"name":              normalized.Name,
		"price":             normalized.Price,
		"discount_price":    normalized.DiscountPrice,
		"tags":              database.StringSlice(normalized.Tags),
		"cover_url":         normalized.CoverURL,
		"screenshot_urls":   database.StringSlice(normalized.ScreenshotURLs),
		"payment_qr_urls":     database.StringSlice(normalized.PaymentQrURLs),
		"verification_url":  normalized.VerificationURL,
		"description":       normalized.Description,
		"active":            normalized.Active,
		"require_login":     normalized.RequireLogin,
		"max_per_account":   normalized.MaxPerAccount,
		"delivery_mode":     normalized.DeliveryMode,
		"fulfillment_type":  normalized.FulfillmentType,
		"fixed_content":     normalized.FixedContent,
		"show_note":         normalized.ShowNote,
		"show_contact":      normalized.ShowContact,
	}).Error; err != nil {
		return models.Product{}, err
	}
	if err := s.replaceCodes(id, normalized.Codes); err != nil {
		return models.Product{}, err
	}

	var updated database.ProductRow
	s.db.First(&updated, "id = ?", id)
	codes, _ := s.loadCodes(id)
	return rowToModel(updated, codes), nil
}

// UpdateTx 在事务内更新商品及卡密列表（与 Update 行为一致）。
func (s *ProductStore) UpdateTx(tx *gorm.DB, id string, patch models.Product) (models.Product, error) {
	var row database.ProductRow
	if err := tx.First(&row, "id = ?", id).Error; err != nil {
		return models.Product{}, fmt.Errorf("product not found")
	}
	normalized := normalizeProduct(patch)

	if err := tx.Model(&row).Updates(map[string]interface{}{
		"name":              normalized.Name,
		"price":             normalized.Price,
		"discount_price":    normalized.DiscountPrice,
		"tags":              database.StringSlice(normalized.Tags),
		"cover_url":         normalized.CoverURL,
		"screenshot_urls":   database.StringSlice(normalized.ScreenshotURLs),
		"payment_qr_urls":   database.StringSlice(normalized.PaymentQrURLs),
		"verification_url":  normalized.VerificationURL,
		"description":       normalized.Description,
		"active":            normalized.Active,
		"require_login":     normalized.RequireLogin,
		"max_per_account":   normalized.MaxPerAccount,
		"delivery_mode":     normalized.DeliveryMode,
		"fulfillment_type":  normalized.FulfillmentType,
		"fixed_content":     normalized.FixedContent,
		"show_note":         normalized.ShowNote,
		"show_contact":      normalized.ShowContact,
	}).Error; err != nil {
		return models.Product{}, err
	}
	if err := s.replaceCodesTx(tx, id, normalized.Codes); err != nil {
		return models.Product{}, err
	}

	var updated database.ProductRow
	tx.First(&updated, "id = ?", id)
	codes, _ := s.loadCodesTx(tx, id)
	return rowToModel(updated, codes), nil
}

func (s *ProductStore) Toggle(id string, active bool) (models.Product, error) {
	if err := s.db.Model(&database.ProductRow{}).Where("id = ?", id).Update("active", active).Error; err != nil {
		return models.Product{}, err
	}
	var row database.ProductRow
	if err := s.db.First(&row, "id = ?", id).Error; err != nil {
		return models.Product{}, fmt.Errorf("product not found")
	}
	codes, _ := s.loadCodes(id)
	return rowToModel(row, codes), nil
}

func (s *ProductStore) IncrementSold(id string, count int) error {
	return s.db.Model(&database.ProductRow{}).Where("id = ?", id).
		UpdateColumn("total_sold", gorm.Expr("total_sold + ?", count)).Error
}

func (s *ProductStore) IncrementView(id, fingerprint string) (models.Product, bool, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	now := time.Now()
	s.cleanupRecentViews(now)
	key := buildViewKey(id, fingerprint)
	if lastViewedAt, ok := s.recentViews[key]; ok && now.Sub(lastViewedAt) < viewCooldown {
		var row database.ProductRow
		if err := s.db.First(&row, "id = ?", id).Error; err != nil {
			return models.Product{}, false, fmt.Errorf("product not found")
		}
		return rowToModel(row, nil), false, nil
	}

	if err := s.db.Model(&database.ProductRow{}).Where("id = ?", id).
		UpdateColumn("view_count", gorm.Expr("view_count + 1")).Error; err != nil {
		return models.Product{}, false, err
	}
	s.recentViews[key] = now

	var row database.ProductRow
	if err := s.db.First(&row, "id = ?", id).Error; err != nil {
		return models.Product{}, false, fmt.Errorf("product not found")
	}
	return rowToModel(row, nil), true, nil
}

// IncrementViewCount 原子地将 view_count +1，并返回更新后的商品（不含卡密）。
// 在 Handler 已用 Redis 去重确认本次为新浏览时调用，故此处无需进程内锁。
func (s *ProductStore) IncrementViewCount(id string) (models.Product, error) {
	if err := s.db.Model(&database.ProductRow{}).Where("id = ?", id).
		UpdateColumn("view_count", gorm.Expr("view_count + 1")).Error; err != nil {
		return models.Product{}, err
	}
	var row database.ProductRow
	if err := s.db.First(&row, "id = ?", id).Error; err != nil {
		return models.Product{}, fmt.Errorf("product not found")
	}
	return rowToModel(row, nil), nil
}

// GetViewState 只读取当前商品状态，不修改任何计数。
// 在 Handler 已通过 Redis 判定为重复访问时使用。
func (s *ProductStore) GetViewState(id string) (models.Product, error) {
	var row database.ProductRow
	if err := s.db.First(&row, "id = ?", id).Error; err != nil {
		return models.Product{}, fmt.Errorf("product not found")
	}
	return rowToModel(row, nil), nil
}

func (s *ProductStore) Delete(id string) error {
	if err := s.db.Where("product_id = ?", id).Delete(&database.ProductCodeRow{}).Error; err != nil {
		return err
	}
	return s.db.Delete(&database.ProductRow{}, "id = ?", id).Error
}

// normalizeProduct 对商品字段进行规范化处理（清理空白、设默认值等）。
func normalizeProduct(item models.Product) models.Product {
	item.CoverURL = strings.TrimSpace(item.CoverURL)
	if item.CoverURL == "" {
		item.CoverURL = defaultCoverURL
	}
	if item.Tags == nil {
		item.Tags = []string{}
	}
	item.Tags = sanitizeTags(item.Tags)
	if item.PaymentQrURLs == nil {
		item.PaymentQrURLs = []string{}
	}
	if item.ScreenshotURLs == nil {
		item.ScreenshotURLs = []string{}
	}
	if len(item.ScreenshotURLs) > maxScreenshotURLs {
		item.ScreenshotURLs = item.ScreenshotURLs[:maxScreenshotURLs]
	}
	item.PaymentQrURLs = sanitizePaymentQrURLs(item.PaymentQrURLs)
	if item.Codes == nil {
		item.Codes = []string{}
	}
	if item.DiscountPrice <= 0 || item.DiscountPrice >= item.Price {
		item.DiscountPrice = 0
	}
	item.VerificationURL = strings.TrimSpace(item.VerificationURL)
	item.FulfillmentType = effectiveFulfillment(item.FulfillmentType)
	if item.FulfillmentType == "fixed" {
		item.FixedContent = strings.TrimSpace(item.FixedContent)
		item.Codes = []string{}
	} else {
		item.FixedContent = ""
		item.Codes = sanitizeCodes(item.Codes)
	}
	item.Quantity = len(item.Codes)
	if item.FulfillmentType == "fixed" {
		item.Quantity = -1
	}
	if item.DeliveryMode == "" || item.DeliveryMode == "manual" {
		item.DeliveryMode = "auto"
	}
	return item
}

func sanitizePaymentQrURLs(urls []string) []string {
	clean := make([]string, 0, len(urls))
	seen := map[string]bool{}
	for _, u := range urls {
		t := strings.TrimSpace(u)
		if t == "" || seen[t] {
			continue
		}
		seen[t] = true
		clean = append(clean, t)
		if len(clean) >= maxPaymentQrURLs {
			break
		}
	}
	return clean
}

func sanitizeCodes(codes []string) []string {
	clean := make([]string, 0, len(codes))
	seen := map[string]bool{}
	for _, code := range codes {
		trimmed := strings.TrimSpace(code)
		if trimmed == "" || seen[trimmed] {
			continue
		}
		seen[trimmed] = true
		clean = append(clean, trimmed)
	}
	return clean
}

func sanitizeTags(tags []string) []string {
	clean := make([]string, 0, len(tags))
	seen := map[string]bool{}
	for _, tag := range tags {
		t := strings.TrimSpace(tag)
		if t == "" {
			continue
		}
		key := strings.ToLower(t)
		if seen[key] {
			continue
		}
		seen[key] = true
		clean = append(clean, t)
		if len(clean) >= 20 {
			break
		}
	}
	return clean
}

func buildViewKey(id, fingerprint string) string {
	sum := sha256.Sum256([]byte(id + "|" + fingerprint))
	return fmt.Sprintf("%x", sum)
}

func (s *ProductStore) cleanupRecentViews(now time.Time) {
	for key, lastViewedAt := range s.recentViews {
		if now.Sub(lastViewedAt) >= viewCooldown {
			delete(s.recentViews, key)
		}
	}
}

