package commerce

import (
	"context"
	"errors"
	"fmt"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"
	"unicode/utf8"

	"gorm.io/gorm"
)

var activityTemplateKeys = map[string]bool{"standard": true, "product_grid": true, "flash_sale": true, "coupon": true}
var activityParticipationModes = map[string]bool{"direct": true, "existing_discount": true, "promo_code": true, "activity_price": true}
var activityAudiences = map[string]bool{"all": true, "new_user": true, "member": true, "agent": true}
var activityCardStyles = map[string]bool{"a": true, "b": true, "c": true, "d": true}

func (s *Service) AdminListActivities(ctx context.Context, query AdminListQuery, published *bool) (map[string]any, error) {
	db := s.db.WithContext(ctx).Model(&Activity{})
	if published != nil {
		db = db.Where("is_published = ?", *published)
	}
	if query.Search != "" {
		like := "%" + query.Search + "%"
		db = db.Where("title LIKE ? OR description LIKE ?", like, like)
	}
	return adminPage(db.Order("sort_order DESC, id DESC"), query, func(items []Activity) []map[string]any {
		out := make([]map[string]any, 0, len(items))
		for _, item := range items {
			out = append(out, s.activityMap(ctx, item, true))
		}
		return out
	})
}

func (s *Service) AdminActivityOptions(ctx context.Context) (map[string]any, error) {
	if err := s.requireDB(); err != nil {
		return nil, err
	}
	type productOption struct {
		ID           int64
		Name         string
		SKU          string
		Status       string
		GroupName    string
		CategoryName string
	}
	var products []productOption
	err := s.db.WithContext(ctx).Table("products p").
		Select("p.id, p.name, p.sku, p.status, COALESCE(pg.name, '') AS group_name, COALESCE(pc.name, '') AS category_name").
		Joins("LEFT JOIN product_groups pg ON pg.id = p.group_id").
		Joins("LEFT JOIN product_categories pc ON pc.id = pg.category_id").
		Order("pc.sort_order ASC, pg.sort_order ASC, p.sort_order ASC, p.id ASC").Scan(&products).Error
	if err != nil {
		return nil, err
	}
	var promos []PromoCode
	if err := s.db.WithContext(ctx).Order("is_enabled DESC, id DESC").Find(&promos).Error; err != nil {
		return nil, err
	}
	productItems := make([]map[string]any, 0, len(products))
	for _, item := range products {
		productItems = append(productItems, map[string]any{
			"id": item.ID, "name": item.Name, "sku": item.SKU, "status": item.Status,
			"group_name": item.GroupName, "category_name": item.CategoryName,
		})
	}
	promoItems := make([]map[string]any, 0, len(promos))
	for _, item := range promos {
		promoItems = append(promoItems, map[string]any{
			"id": item.ID, "code": item.Code, "description": item.Description, "type": item.Type,
			"value": item.Value, "is_enabled": item.IsEnabled, "expires_at": item.ExpiresAt,
		})
	}
	return map[string]any{"products": productItems, "promo_codes": promoItems}, nil
}

func (s *Service) AdminSaveActivity(ctx context.Context, id int64, payload map[string]any, adminID int64) (map[string]any, error) {
	payload = cleanRawPayload(payload)
	var item Activity
	if id > 0 {
		if err := s.db.WithContext(ctx).Where("id = ?", id).First(&item).Error; err != nil {
			if errors.Is(err, gorm.ErrRecordNotFound) {
				return nil, NotFound("activity not found")
			}
			return nil, err
		}
	} else {
		item.LimitedTime = true
		item.IsPublished = true
		item.ShowOnHome = true
		item.TemplateKey = "standard"
		item.ButtonText = "立即参与"
		item.ParticipationMode = "direct"
		item.Audience = "all"
		if adminID > 0 {
			item.CreatedBy = &adminID
		}
	}

	applyActivityPayload(&item, payload)
	if err := validateActivity(item); err != nil {
		return nil, err
	}
	if !item.LimitedTime {
		item.StartsAt = nil
		item.EndsAt = nil
	}
	if item.ParticipationMode != "promo_code" {
		item.PromoCodeID = nil
	}

	products, replaceProducts, err := parseActivityProducts(payload)
	if err != nil {
		return nil, err
	}
	// Empty product arrays from partial-edit clients must not erase existing
	// activity products unless the caller explicitly confirms replacement.
	if id > 0 && replaceProducts && len(products) == 0 && !boolValueAny(payload["replace_products"], false) {
		replaceProducts = false
	}
	err = s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if item.PromoCodeID != nil {
			var count int64
			if err := tx.Model(&PromoCode{}).Where("id = ?", *item.PromoCodeID).Count(&count).Error; err != nil {
				return err
			}
			if count == 0 {
				return BadRequest("选择的优惠码不存在")
			}
		}
		if replaceProducts && len(products) > 0 {
			ids := make([]int64, 0, len(products))
			for _, product := range products {
				ids = append(ids, product.ProductID)
			}
			var count int64
			if err := tx.Model(&Product{}).Where("id IN ?", ids).Count(&count).Error; err != nil {
				return err
			}
			if count != int64(len(ids)) {
				return BadRequest("参与活动的商品中包含不存在的商品")
			}
		}
		if id > 0 {
			if err := tx.Save(&item).Error; err != nil {
				return err
			}
		} else if err := tx.Create(&item).Error; err != nil {
			return err
		}
		if !replaceProducts {
			return nil
		}
		if err := tx.Where("activity_id = ?", item.ID).Delete(&ActivityProduct{}).Error; err != nil {
			return err
		}
		for index := range products {
			products[index].ActivityID = item.ID
		}
		if len(products) > 0 {
			return tx.Create(&products).Error
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return s.activityMap(ctx, item, true), nil
}

func (s *Service) AdminDeleteActivity(ctx context.Context, id int64) (map[string]any, error) {
	err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		if err := tx.Where("activity_id = ?", id).Delete(&ActivityProduct{}).Error; err != nil {
			return err
		}
		result := tx.Where("id = ?", id).Delete(&Activity{})
		if result.Error != nil {
			return result.Error
		}
		if result.RowsAffected == 0 {
			return NotFound("activity not found")
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return map[string]any{"deleted": true, "id": id}, nil
}

func (s *Service) ListActivities(ctx context.Context, status string) ([]map[string]any, error) {
	if err := s.requireDB(); err != nil {
		return nil, err
	}
	var items []Activity
	if err := s.db.WithContext(ctx).Where("is_published = ?", true).Order("is_featured DESC, sort_order DESC, id DESC").Find(&items).Error; err != nil {
		return nil, err
	}
	now := time.Now().UTC()
	out := make([]map[string]any, 0, len(items))
	for _, item := range items {
		current, _ := activityStatus(item, now)
		if status != "" && status != "all" && current != status {
			continue
		}
		out = append(out, s.activityMap(ctx, item, false))
	}
	return out, nil
}

func (s *Service) GetActivity(ctx context.Context, id int64) (map[string]any, error) {
	if err := s.requireDB(); err != nil {
		return nil, err
	}
	var item Activity
	if err := s.db.WithContext(ctx).Where("id = ? AND is_published = ?", id, true).First(&item).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, NotFound("活动不存在或尚未发布")
		}
		return nil, err
	}
	return s.activityMap(ctx, item, true), nil
}

func applyActivityPayload(item *Activity, payload map[string]any) {
	stringFields := map[string]*string{
		"title": &item.Title, "description": &item.Description, "template_key": &item.TemplateKey,
		"button_text": &item.ButtonText, "participation_mode": &item.ParticipationMode, "audience": &item.Audience,
	}
	for key, target := range stringFields {
		if _, ok := payload[key]; ok {
			*target = stringFromMap(payload, key)
		}
	}
	ptrFields := map[string]**string{
		"subtitle": &item.Subtitle, "content": &item.Content, "image_url": &item.ImageURL,
		"banner_url": &item.BannerURL, "target_url": &item.TargetURL, "label": &item.Label, "rules": &item.Rules,
	}
	for key, target := range ptrFields {
		if _, ok := payload[key]; ok {
			*target = stringPtrFromMap(payload, key)
		}
	}
	if _, ok := payload["promo_code_id"]; ok {
		if value, valid := int64FromAny(payload["promo_code_id"]); valid && value > 0 {
			item.PromoCodeID = &value
		} else {
			item.PromoCodeID = nil
		}
	}
	if _, ok := payload["limited_time"]; ok {
		item.LimitedTime = boolValueAny(payload["limited_time"], item.LimitedTime)
	}
	if _, ok := payload["starts_at"]; ok {
		item.StartsAt = timePtrFromMap(payload, "starts_at")
	}
	if _, ok := payload["ends_at"]; ok {
		item.EndsAt = timePtrFromMap(payload, "ends_at")
	}
	if _, ok := payload["is_published"]; ok {
		item.IsPublished = boolValueAny(payload["is_published"], item.IsPublished)
	}
	if _, ok := payload["is_featured"]; ok {
		item.IsFeatured = boolValueAny(payload["is_featured"], item.IsFeatured)
	}
	if _, ok := payload["show_on_home"]; ok {
		item.ShowOnHome = boolValueAny(payload["show_on_home"], item.ShowOnHome)
	}
	if _, ok := payload["sort_order"]; ok {
		item.SortOrder = intValueAny(payload["sort_order"], item.SortOrder)
	}
	if _, ok := payload["per_user_limit"]; ok {
		item.PerUserLimit = intValueAny(payload["per_user_limit"], item.PerUserLimit)
	}
	if _, ok := payload["total_limit"]; ok {
		item.TotalLimit = intValueAny(payload["total_limit"], item.TotalLimit)
	}
}

func parseActivityProducts(payload map[string]any) ([]ActivityProduct, bool, error) {
	raw, exists := payload["products"]
	if !exists {
		return nil, false, nil
	}
	list, ok := raw.([]any)
	if !ok {
		return nil, true, BadRequest("参与商品格式不正确")
	}
	items := make([]ActivityProduct, 0, len(list))
	seen := map[int64]bool{}
	for index, rawItem := range list {
		data, ok := rawItem.(map[string]any)
		if !ok {
			return nil, true, BadRequest(fmt.Sprintf("第 %d 个参与商品格式不正确", index+1))
		}
		productID, valid := int64FromAny(data["product_id"])
		if !valid || productID <= 0 {
			return nil, true, BadRequest(fmt.Sprintf("第 %d 个参与商品未选择商品", index+1))
		}
		if seen[productID] {
			return nil, true, BadRequest("同一个商品不能重复加入活动")
		}
		seen[productID] = true
		item := ActivityProduct{
			ProductID: productID, SectionName: stringFromMap(data, "section_name"), CardStyle: stringFromMap(data, "card_style"),
			SectionDescription: stringPtrFromMap(data, "section_description"), CustomLabel: stringPtrFromMap(data, "custom_label"),
			ProductDescription: stringPtrFromMap(data, "product_description"), BillingCycle: stringPtrFromMap(data, "billing_cycle"),
			StockLimit: intValueAny(data["stock_limit"], 0), PerUserLimit: intValueAny(data["per_user_limit"], 0),
			SortOrder: intValueAny(data["sort_order"], index),
		}
		if item.SectionName == "" {
			item.SectionName = "精选产品"
		}
		if item.CardStyle == "" {
			item.CardStyle = "a"
		}
		if !activityCardStyles[item.CardStyle] {
			return nil, true, BadRequest("商品卡片样式无效")
		}
		if value, valid := float64FromAny(data["activity_price"]); valid {
			item.ActivityPrice = &value
		}
		if value, valid := float64FromAny(data["discount_percent"]); valid {
			item.DiscountPercent = &value
		}
		if item.ActivityPrice != nil && *item.ActivityPrice < 0 {
			return nil, true, BadRequest("活动价不能小于 0")
		}
		if item.DiscountPercent != nil && (*item.DiscountPercent <= 0 || *item.DiscountPercent > 100) {
			return nil, true, BadRequest("折扣比例需要在 0 到 100 之间")
		}
		if item.StockLimit < 0 || item.PerUserLimit < 0 {
			return nil, true, BadRequest("商品库存与限购数量不能小于 0")
		}
		items = append(items, item)
	}
	return items, true, nil
}

func validateActivity(item Activity) error {
	if strings.TrimSpace(item.Title) == "" {
		return BadRequest("活动标题不能为空")
	}
	if utf8.RuneCountInString(item.Title) > 128 {
		return BadRequest("活动标题不能超过 128 个字符")
	}
	if strings.TrimSpace(item.Description) == "" {
		return BadRequest("活动描述不能为空")
	}
	if utf8.RuneCountInString(item.Description) > 2000 {
		return BadRequest("活动描述不能超过 2000 个字符")
	}
	if item.Subtitle != nil && utf8.RuneCountInString(*item.Subtitle) > 255 {
		return BadRequest("活动副标题不能超过 255 个字符")
	}
	for _, value := range []*string{item.ImageURL, item.BannerURL, item.TargetURL} {
		if value != nil && utf8.RuneCountInString(*value) > 500 {
			return BadRequest("活动图片或跳转地址不能超过 500 个字符")
		}
	}
	if item.Label != nil && utf8.RuneCountInString(*item.Label) > 32 {
		return BadRequest("活动标签不能超过 32 个字符")
	}
	if !activityTemplateKeys[item.TemplateKey] {
		return BadRequest("活动模板无效")
	}
	if !activityParticipationModes[item.ParticipationMode] {
		return BadRequest("参与方式无效")
	}
	if item.ParticipationMode == "promo_code" && item.PromoCodeID == nil {
		return BadRequest("优惠码参与方式必须选择优惠码")
	}
	if !activityAudiences[item.Audience] {
		return BadRequest("参与用户范围无效")
	}
	if item.PerUserLimit < 0 || item.TotalLimit < 0 {
		return BadRequest("活动限额不能小于 0")
	}
	if item.SortOrder < -9999 || item.SortOrder > 9999 {
		return BadRequest("排序值需要在 -9999 到 9999 之间")
	}
	if item.LimitedTime {
		if item.StartsAt == nil || item.EndsAt == nil {
			return BadRequest("限时活动需要填写开始时间和结束时间")
		}
		if !item.EndsAt.After(*item.StartsAt) {
			return BadRequest("结束时间必须晚于开始时间")
		}
	}
	return nil
}

func activityStatus(item Activity, now time.Time) (string, string) {
	if !item.IsPublished {
		return "draft", "未发布"
	}
	if !item.LimitedTime {
		return "ongoing", "正在进行"
	}
	if item.StartsAt != nil && now.Before(*item.StartsAt) {
		return "upcoming", "活动预告"
	}
	if item.EndsAt != nil && !now.Before(*item.EndsAt) {
		return "ended", "已结束"
	}
	return "ongoing", "正在进行"
}

func (s *Service) activityMap(ctx context.Context, item Activity, includeProducts bool) map[string]any {
	status, statusText := activityStatus(item, time.Now().UTC())
	data := map[string]any{
		"id": item.ID, "title": item.Title, "subtitle": item.Subtitle, "description": item.Description,
		"content": item.Content, "image_url": item.ImageURL, "banner_url": item.BannerURL, "target_url": item.TargetURL,
		"label": item.Label, "template_key": item.TemplateKey, "button_text": item.ButtonText,
		"participation_mode": item.ParticipationMode, "promo_code_id": item.PromoCodeID, "audience": item.Audience,
		"rules": item.Rules, "per_user_limit": item.PerUserLimit, "total_limit": item.TotalLimit,
		"limited_time": item.LimitedTime, "starts_at": item.StartsAt, "ends_at": item.EndsAt,
		"is_published": item.IsPublished, "is_featured": item.IsFeatured, "show_on_home": item.ShowOnHome,
		"sort_order": item.SortOrder, "created_by": item.CreatedBy, "status": status, "status_text": statusText,
		"created_at": item.CreatedAt, "updated_at": item.UpdatedAt, "detail_url": fmt.Sprintf("/activities/%d", item.ID),
	}
	if item.PromoCodeID != nil {
		var promo PromoCode
		if s.db.WithContext(ctx).Where("id = ?", *item.PromoCodeID).First(&promo).Error == nil {
			data["promo_code"] = map[string]any{"id": promo.ID, "code": promo.Code, "description": promo.Description, "type": promo.Type, "value": promo.Value}
		}
	}
	if includeProducts {
		data["products"] = s.activityProductMaps(ctx, item.ID)
	}
	return data
}

func (s *Service) activityProductMaps(ctx context.Context, activityID int64) []map[string]any {
	var links []ActivityProduct
	if err := s.db.WithContext(ctx).Where("activity_id = ?", activityID).Order("sort_order ASC, id ASC").Find(&links).Error; err != nil || len(links) == 0 {
		return []map[string]any{}
	}
	ids := make([]int64, 0, len(links))
	for _, link := range links {
		ids = append(ids, link.ProductID)
	}
	type productView struct {
		Product
		GroupSlug    string `gorm:"column:group_slug"`
		GroupName    string `gorm:"column:group_name"`
		CategorySlug string `gorm:"column:category_slug"`
		CategoryName string `gorm:"column:category_name"`
	}
	var products []productView
	_ = s.db.WithContext(ctx).Table("products p").Select("p.*, pg.slug AS group_slug, pg.name AS group_name, pc.slug AS category_slug, pc.name AS category_name").
		Joins("LEFT JOIN product_groups pg ON pg.id = p.group_id").Joins("LEFT JOIN product_categories pc ON pc.id = pg.category_id").Where("p.id IN ?", ids).Scan(&products).Error
	productByID := map[int64]productView{}
	for _, product := range products {
		productByID[product.ID] = product
	}
	var pricings []ProductPricing
	_ = s.db.WithContext(ctx).Where("product_id IN ? AND is_enabled = ?", ids, true).Order("sort_order ASC, id ASC").Find(&pricings).Error
	pricingByProduct := map[int64][]ProductPricing{}
	for _, pricing := range pricings {
		pricingByProduct[pricing.ProductID] = append(pricingByProduct[pricing.ProductID], pricing)
	}
	out := make([]map[string]any, 0, len(links))
	for _, link := range links {
		product, ok := productByID[link.ProductID]
		if !ok {
			continue
		}
		selected := selectActivityPricing(pricingByProduct[product.ID], link.BillingCycle)
		storeURL := "/store"
		if product.CategorySlug != "" && product.GroupSlug != "" {
			params := url.Values{}
			params.Set("category", product.CategorySlug)
			params.Set("group", product.GroupSlug)
			params.Set("sku", product.SKU)
			storeURL = "/store?" + params.Encode()
		}
		out = append(out, map[string]any{
			"id": link.ID, "product_id": product.ID, "name": product.Name, "sku": product.SKU, "status": product.Status,
			"section_name": link.SectionName, "section_description": link.SectionDescription, "card_style": link.CardStyle,
			"custom_label": link.CustomLabel, "product_description": link.ProductDescription, "billing_cycle": link.BillingCycle,
			"activity_price": link.ActivityPrice, "discount_percent": link.DiscountPercent, "stock_limit": link.StockLimit,
			"per_user_limit": link.PerUserLimit, "sort_order": link.SortOrder, "stock": product.Stock,
			"stock_policy": product.StockPolicy, "vcpu": product.VCPU, "memory": product.Memory, "disk": product.Disk,
			"bandwidth": product.Bandwidth, "features": product.Features, "badges": product.Badges,
			"group_name": product.GroupName, "category_name": product.CategoryName, "store_url": storeURL,
			"pricing": selected, "pricings": pricingByProduct[product.ID],
		})
	}
	return out
}

func selectActivityPricing(items []ProductPricing, cycle *string) any {
	if len(items) == 0 {
		return nil
	}
	if cycle != nil {
		for _, item := range items {
			if item.Cycle == *cycle {
				return item
			}
		}
	}
	sort.SliceStable(items, func(i, j int) bool { return items[i].SortOrder < items[j].SortOrder })
	return items[0]
}

func float64FromAny(value any) (float64, bool) {
	switch typed := value.(type) {
	case float64:
		return typed, true
	case float32:
		return float64(typed), true
	case int:
		return float64(typed), true
	case int64:
		return float64(typed), true
	case string:
		if strings.TrimSpace(typed) == "" {
			return 0, false
		}
		parsed, err := strconv.ParseFloat(strings.TrimSpace(typed), 64)
		return parsed, err == nil
	default:
		return 0, false
	}
}
