package ledger

import (
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"unicode/utf8"
)

const (
	vipPresentationDescriptionMaxRunes = 1000
	vipPresentationPreviewMaxCount     = 20
	vipPresentationTextMaxRunes        = 128
	vipPresentationURLMaxRunes         = 2048
)

// ParseVipBenefitPresentation 只解析 metadata_json.presentation 这一段稳定展示契约。
// metadata_json 的其它业务字段（例如 coin_amount、message）由对应 owner 保留处理。
func ParseVipBenefitPresentation(metadataJSON string) (*VipBenefitPresentation, error) {
	metadataJSON = strings.TrimSpace(metadataJSON)
	if metadataJSON == "" {
		return nil, nil
	}
	var metadata struct {
		Presentation *VipBenefitPresentation `json:"presentation"`
	}
	if err := json.Unmarshal([]byte(metadataJSON), &metadata); err != nil {
		return nil, err
	}
	if metadata.Presentation == nil {
		return nil, nil
	}
	if err := ValidateVipBenefitPresentation(*metadata.Presentation); err != nil {
		return nil, err
	}
	return metadata.Presentation, nil
}

// MergeVipBenefitPresentation 将后台结构化输入归一到现有 JSON 列，避免为纯展示字段增加双写事实。
// presentation 为空对象表示显式清除；nil 表示沿用 metadata_json 中已有值。
func MergeVipBenefitPresentation(metadataJSON string, presentation *VipBenefitPresentation) (string, error) {
	metadataJSON = strings.TrimSpace(metadataJSON)
	if presentation == nil {
		if metadataJSON == "" {
			return "", nil
		}
		if !json.Valid([]byte(metadataJSON)) {
			return "", errors.New("metadata_json is invalid")
		}
		return metadataJSON, nil
	}
	if err := ValidateVipBenefitPresentation(*presentation); err != nil {
		return "", err
	}

	metadata := make(map[string]json.RawMessage)
	if metadataJSON != "" {
		if err := json.Unmarshal([]byte(metadataJSON), &metadata); err != nil || metadata == nil {
			return "", errors.New("metadata_json must be an object")
		}
	}
	if vipBenefitPresentationIsZero(*presentation) {
		delete(metadata, "presentation")
	} else {
		encoded, err := json.Marshal(presentation)
		if err != nil {
			return "", err
		}
		metadata["presentation"] = encoded
	}
	if len(metadata) == 0 {
		return "", nil
	}
	encoded, err := json.Marshal(metadata)
	return string(encoded), err
}

// ValidateVipBenefitPresentation 锁定 Flutter 可直接渲染的边界，防止后台把任意 JSON
// 重新变成客户端按 benefit_code 猜类型的隐式协议。
func ValidateVipBenefitPresentation(presentation VipBenefitPresentation) error {
	presentation.Description = strings.TrimSpace(presentation.Description)
	if utf8.RuneCountInString(presentation.Description) > vipPresentationDescriptionMaxRunes {
		return errors.New("presentation description is too long")
	}
	if len(presentation.PreviewItems) > vipPresentationPreviewMaxCount {
		return errors.New("presentation has too many preview items")
	}
	seen := make(map[string]struct{}, len(presentation.PreviewItems))
	for index, item := range presentation.PreviewItems {
		item.PreviewID = strings.TrimSpace(item.PreviewID)
		item.Title = strings.TrimSpace(item.Title)
		item.MediaType = strings.ToLower(strings.TrimSpace(item.MediaType))
		item.CompositionType = strings.ToLower(strings.TrimSpace(item.CompositionType))
		if item.PreviewID == "" || utf8.RuneCountInString(item.PreviewID) > vipPresentationTextMaxRunes {
			return fmt.Errorf("preview_items[%d].preview_id is invalid", index)
		}
		if _, exists := seen[item.PreviewID]; exists {
			return fmt.Errorf("preview_items[%d].preview_id is duplicated", index)
		}
		seen[item.PreviewID] = struct{}{}
		if utf8.RuneCountInString(item.Title) > vipPresentationTextMaxRunes {
			return fmt.Errorf("preview_items[%d].title is too long", index)
		}
		if item.MediaType != "image" && item.MediaType != "animation" {
			return fmt.Errorf("preview_items[%d].media_type is invalid", index)
		}
		switch item.CompositionType {
		case "none", "user_avatar_center", "user_avatar_waveform":
		default:
			return fmt.Errorf("preview_items[%d].composition_type is invalid", index)
		}
		if exceedsRuneLimit(item.AssetURL, vipPresentationURLMaxRunes) || exceedsRuneLimit(item.PreviewURL, vipPresentationURLMaxRunes) || exceedsRuneLimit(item.AnimationURL, vipPresentationURLMaxRunes) {
			return fmt.Errorf("preview_items[%d] url is too long", index)
		}
		if item.MediaType == "animation" && strings.TrimSpace(item.AnimationURL) == "" {
			return fmt.Errorf("preview_items[%d].animation_url is required", index)
		}
		if item.MediaType == "image" && strings.TrimSpace(item.PreviewURL) == "" && strings.TrimSpace(item.AssetURL) == "" {
			return fmt.Errorf("preview_items[%d] image url is required", index)
		}
	}
	if reward := presentation.NumericReward; reward != nil {
		reward.Label = strings.TrimSpace(reward.Label)
		reward.Unit = strings.TrimSpace(reward.Unit)
		reward.Period = strings.ToLower(strings.TrimSpace(reward.Period))
		if reward.Value <= 0 || reward.Label == "" || reward.Unit == "" || utf8.RuneCountInString(reward.Label) > vipPresentationTextMaxRunes || utf8.RuneCountInString(reward.Unit) > vipPresentationTextMaxRunes {
			return errors.New("numeric_reward is invalid")
		}
		switch reward.Period {
		case "once", "daily", "monthly":
		default:
			return errors.New("numeric_reward.period is invalid")
		}
	}
	return nil
}

func vipBenefitPresentationIsZero(presentation VipBenefitPresentation) bool {
	return strings.TrimSpace(presentation.Description) == "" && len(presentation.PreviewItems) == 0 && presentation.NumericReward == nil
}

func exceedsRuneLimit(value string, limit int) bool {
	return utf8.RuneCountInString(strings.TrimSpace(value)) > limit
}
