package user

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"strings"
	"time"

	userv1 "hyapp.local/api/proto/user/v1"
	"hyapp/pkg/appcode"
	"hyapp/pkg/giftlimits"
	"hyapp/pkg/xerr"
	userdomain "hyapp/services/user-service/internal/domain/user"
)

const (
	animatedAvatarBenefitCode = "animated_avatar"
	tieredPrivilegeVIPProgram = "tiered_privilege_v1"
	avatarUploadStatusActive  = "active"
	avatarUploadMaxBytes      = int64(5 << 20)
	avatarUploadMaxFrames     = int32(120)
	avatarUploadMaxDurationMS = int64(15_000)
	avatarVIPLookupTimeout    = time.Second
)

// AuthorizeUserAvatarUpload 在对象写入前持久化媒体事实，并只对服务端解析出的动态 GIF/WebP 查询精确权益。
func (s *Service) AuthorizeUserAvatarUpload(ctx context.Context, requestID string, userID int64, commandID string, media userdomain.AvatarMedia) (userdomain.AvatarUploadRegistration, error) {
	if userID <= 0 || !giftlimits.ValidCommandID(strings.TrimSpace(commandID)) {
		return userdomain.AvatarUploadRegistration{}, xerr.New(xerr.InvalidArgument, "avatar upload request is invalid")
	}
	if s.avatarUploadRepository == nil {
		return userdomain.AvatarUploadRegistration{}, xerr.New(xerr.Unavailable, "avatar upload repository is unavailable")
	}
	if err := s.requireCompletedProfile(ctx, userID); err != nil {
		// 注册头像仍走 public 静态入口；VIP 动态头像只能由已完成资料且已登录的用户上传。
		return userdomain.AvatarUploadRegistration{}, err
	}
	media = normalizeAvatarMedia(media)
	if err := validateAvatarMedia(media, false); err != nil {
		return userdomain.AvatarUploadRegistration{}, err
	}

	appCode := appcode.FromContext(ctx)
	uploadID := deriveAvatarUploadID(appCode, userID, commandID)
	payloadSHA := avatarMediaPayloadSHA256(media)
	registration := userdomain.AvatarUploadRegistration{
		AppCode: appCode, UserID: userID, UploadID: uploadID, ClientCommandID: strings.TrimSpace(commandID),
		MediaPayloadSHA256: payloadSHA, Media: media,
	}
	registration.ExpectedObjectKey = avatarObjectKey(appCode, userID, uploadID, media)

	if existing, exists, err := s.avatarUploadRepository.GetAvatarUpload(ctx, userID, uploadID); err != nil {
		return userdomain.AvatarUploadRegistration{}, err
	} else if exists {
		if !sameAvatarUploadIdentity(existing, registration) {
			return userdomain.AvatarUploadRegistration{}, xerr.New(xerr.IdempotencyConflict, "avatar upload command payload conflict")
		}
		if existing.Status == avatarUploadStatusActive {
			// 已完成上传的同载荷重试返回首次结果，不能因用户 VIP 后续变化破坏成功请求的幂等性。
			return existing, nil
		}
		if existing.Status != "authorized" {
			return userdomain.AvatarUploadRegistration{}, xerr.New(xerr.Conflict, "avatar upload is already consumed")
		}
	}
	if media.Animated {
		if err := s.requireAnimatedAvatarBenefit(ctx, requestID, userID, "upload_animated_avatar"); err != nil {
			return userdomain.AvatarUploadRegistration{}, err
		}
	}

	nowMS := s.now().UnixMilli()
	registration.CreatedAtMS, registration.UpdatedAtMS = nowMS, nowMS
	saved, err := s.avatarUploadRepository.RegisterAvatarUpload(ctx, registration)
	if errors.Is(err, userdomain.ErrAvatarUploadCommandConflict) {
		return userdomain.AvatarUploadRegistration{}, xerr.New(xerr.IdempotencyConflict, "avatar upload command payload conflict")
	}
	return saved, err
}

// CompleteUserAvatarUpload 只推进首次授权的对象键；URL 和媒体属性必须与授权载荷逐字段一致。
func (s *Service) CompleteUserAvatarUpload(ctx context.Context, userID int64, uploadID string, media userdomain.AvatarMedia) (userdomain.AvatarUploadRegistration, error) {
	uploadID = strings.TrimSpace(uploadID)
	if userID <= 0 || !giftlimits.ValidCommandID(uploadID) {
		return userdomain.AvatarUploadRegistration{}, xerr.New(xerr.InvalidArgument, "avatar upload completion is invalid")
	}
	if s.avatarUploadRepository == nil {
		return userdomain.AvatarUploadRegistration{}, xerr.New(xerr.Unavailable, "avatar upload repository is unavailable")
	}
	media = normalizeAvatarMedia(media)
	if err := validateAvatarMedia(media, true); err != nil {
		return userdomain.AvatarUploadRegistration{}, err
	}
	existing, exists, err := s.avatarUploadRepository.GetAvatarUpload(ctx, userID, uploadID)
	if err != nil {
		return userdomain.AvatarUploadRegistration{}, err
	}
	if !exists {
		return userdomain.AvatarUploadRegistration{}, xerr.New(xerr.NotFound, "avatar upload authorization not found")
	}
	candidate := userdomain.AvatarUploadRegistration{
		AppCode: appcode.FromContext(ctx), UserID: userID, UploadID: uploadID,
		MediaPayloadSHA256: avatarMediaPayloadSHA256(media), ExpectedObjectKey: media.ObjectKey, ObjectURL: media.URL, Media: media,
	}
	if !sameAvatarUploadIdentity(existing, candidate) || !sameAvatarMediaProperties(existing.Media, media) {
		return userdomain.AvatarUploadRegistration{}, xerr.New(xerr.IdempotencyConflict, "avatar upload payload conflict")
	}
	if existing.Status == avatarUploadStatusActive {
		if strings.TrimSpace(existing.ObjectURL) != media.URL {
			return userdomain.AvatarUploadRegistration{}, xerr.New(xerr.IdempotencyConflict, "avatar upload completion conflict")
		}
		return existing, nil
	}
	if existing.Status != "authorized" {
		return userdomain.AvatarUploadRegistration{}, xerr.New(xerr.Conflict, "avatar upload is already consumed")
	}
	existing.ObjectURL, existing.Media.URL, existing.Media.ObjectKey = media.URL, media.URL, media.ObjectKey
	existing.Status, existing.Media.Status, existing.UpdatedAtMS = avatarUploadStatusActive, avatarUploadStatusActive, s.now().UnixMilli()
	completed, err := s.avatarUploadRepository.CompleteAvatarUpload(ctx, existing)
	if errors.Is(err, userdomain.ErrAvatarUploadCommandConflict) {
		return userdomain.AvatarUploadRegistration{}, xerr.New(xerr.IdempotencyConflict, "avatar upload completion conflict")
	}
	return completed, err
}

// requireAnimatedAvatarBenefit 只对 wallet 配置为权益制的 App 执行动态头像门禁。
// Fami 当前所需等级由 Wallet benefit 配置返回，user-service 不比较 vip_level。
func (s *Service) requireAnimatedAvatarBenefit(ctx context.Context, requestID string, userID int64, action string) error {
	if s.vipBenefitDecisionChecker == nil {
		return xerr.New(xerr.Unavailable, "wallet vip benefit checker is unavailable")
	}
	lookupCtx, cancel := context.WithTimeout(ctx, avatarVIPLookupTimeout)
	defer cancel()
	decision, err := s.vipBenefitDecisionChecker.CheckVIPBenefitDecision(lookupCtx, strings.TrimSpace(requestID), userID, animatedAvatarBenefitCode)
	if err != nil {
		return xerr.New(xerr.Unavailable, "check vip benefit failed")
	}
	if strings.TrimSpace(decision.ProgramType) != tieredPrivilegeVIPProgram {
		return nil
	}
	if decision.Allowed {
		return nil
	}
	return xerr.NewWithMetadata(xerr.VIPBenefitRequired, "vip benefit is required", map[string]string{
		"benefit_code": animatedAvatarBenefitCode, "required_level": fmt.Sprintf("%d", decision.RequiredLevel),
		"current_effective_level": fmt.Sprintf("%d", decision.CurrentEffectiveLevel), "action": strings.TrimSpace(action),
	})
}

// avatarUploadRequiredForRawURL 判断当前 App 是否禁止直接写非空头像 URL。
// nil checker 仅兼容尚未装配 Wallet 的旧测试/本地构造；生产启用 Wallet 后，权益制 App 必须提交上传凭证。
func (s *Service) avatarUploadRequiredForRawURL(ctx context.Context, requestID string, userID int64) (bool, error) {
	if s.vipBenefitDecisionChecker == nil {
		return false, nil
	}
	lookupCtx, cancel := context.WithTimeout(ctx, avatarVIPLookupTimeout)
	defer cancel()
	decision, err := s.vipBenefitDecisionChecker.CheckVIPBenefitDecision(lookupCtx, strings.TrimSpace(requestID), userID, animatedAvatarBenefitCode)
	if err != nil {
		return false, xerr.New(xerr.Unavailable, "check vip program failed")
	}
	return strings.TrimSpace(decision.ProgramType) == tieredPrivilegeVIPProgram, nil
}

func validateAvatarMedia(media userdomain.AvatarMedia, requireUploaded bool) error {
	if media.SizeBytes <= 0 || media.SizeBytes > avatarUploadMaxBytes || media.Width <= 0 || media.Height <= 0 ||
		media.Width > 4096 || media.Height > 4096 || int64(media.Width)*int64(media.Height) > 16_777_216 ||
		media.FrameCount <= 0 || media.FrameCount > avatarUploadMaxFrames || len(media.SHA256) != 64 {
		return xerr.New(xerr.InvalidArgument, "avatar media properties are invalid")
	}
	validType := (media.Format == "jpeg" && media.ContentType == "image/jpeg") ||
		(media.Format == "png" && media.ContentType == "image/png") ||
		(media.Format == "gif" && media.ContentType == "image/gif") ||
		(media.Format == "webp" && media.ContentType == "image/webp")
	if !validType || (media.Animated && media.Format != "gif" && media.Format != "webp") {
		return xerr.New(xerr.InvalidArgument, "avatar media format is invalid")
	}
	if media.Animated {
		if media.FrameCount < 2 || media.DurationMS <= 0 || media.DurationMS > avatarUploadMaxDurationMS {
			return xerr.New(xerr.InvalidArgument, "avatar animation properties are invalid")
		}
	} else if media.FrameCount != 1 || media.DurationMS != 0 {
		return xerr.New(xerr.InvalidArgument, "static avatar properties are invalid")
	}
	if requireUploaded {
		if media.URL == "" || media.ObjectKey == "" || media.Status != avatarUploadStatusActive {
			return xerr.New(xerr.InvalidArgument, "uploaded avatar media is incomplete")
		}
	} else if media.URL != "" || media.ObjectKey != "" || media.Status != avatarUploadStatusActive {
		return xerr.New(xerr.InvalidArgument, "avatar authorization requires pre-upload metadata")
	}
	return nil
}

func normalizeAvatarMedia(media userdomain.AvatarMedia) userdomain.AvatarMedia {
	media.URL = strings.TrimSpace(media.URL)
	media.ObjectKey = strings.TrimLeft(strings.TrimSpace(media.ObjectKey), "/")
	media.ContentType = strings.ToLower(strings.TrimSpace(media.ContentType))
	media.Format = strings.ToLower(strings.TrimSpace(media.Format))
	media.SHA256 = strings.ToLower(strings.TrimSpace(media.SHA256))
	media.Status = strings.ToLower(strings.TrimSpace(media.Status))
	return media
}

func deriveAvatarUploadID(appCode string, userID int64, commandID string) string {
	sum := sha256.Sum256([]byte(fmt.Sprintf("%s:%d:%s", appcode.Normalize(appCode), userID, strings.TrimSpace(commandID))))
	return "avatar_" + hex.EncodeToString(sum[:16])
}

func avatarObjectKey(appCode string, userID int64, uploadID string, media userdomain.AvatarMedia) string {
	extension := media.Format
	if extension == "jpeg" {
		extension = "jpg"
	}
	return fmt.Sprintf("user-media/%s/avatars/%d/%s_%s.%s", appcode.Normalize(appCode), userID, uploadID, media.SHA256[:16], extension)
}

func avatarMediaPayloadSHA256(media userdomain.AvatarMedia) string {
	value := fmt.Sprintf("%s|%s|%d|%d|%d|%t|%d|%d|%s", media.ContentType, media.Format, media.SizeBytes,
		media.Width, media.Height, media.Animated, media.FrameCount, media.DurationMS, media.SHA256)
	sum := sha256.Sum256([]byte(value))
	return hex.EncodeToString(sum[:])
}

func sameAvatarUploadIdentity(left userdomain.AvatarUploadRegistration, right userdomain.AvatarUploadRegistration) bool {
	return appcode.Normalize(left.AppCode) == appcode.Normalize(right.AppCode) && left.UserID == right.UserID &&
		strings.TrimSpace(left.UploadID) == strings.TrimSpace(right.UploadID) &&
		strings.EqualFold(strings.TrimSpace(left.MediaPayloadSHA256), strings.TrimSpace(right.MediaPayloadSHA256)) &&
		strings.TrimLeft(strings.TrimSpace(left.ExpectedObjectKey), "/") == strings.TrimLeft(strings.TrimSpace(right.ExpectedObjectKey), "/")
}

func sameAvatarMediaProperties(left userdomain.AvatarMedia, right userdomain.AvatarMedia) bool {
	left, right = normalizeAvatarMedia(left), normalizeAvatarMedia(right)
	return left.ObjectKey == right.ObjectKey && left.ContentType == right.ContentType && left.Format == right.Format &&
		left.SizeBytes == right.SizeBytes && left.Width == right.Width && left.Height == right.Height &&
		left.Animated == right.Animated && left.FrameCount == right.FrameCount && left.DurationMS == right.DurationMS && left.SHA256 == right.SHA256
}

// AvatarMediaFromProto 把内部 gRPC contract 转成领域媒体事实。
func AvatarMediaFromProto(media *userv1.UserAvatarMedia) userdomain.AvatarMedia {
	if media == nil {
		return userdomain.AvatarMedia{}
	}
	return userdomain.AvatarMedia{
		URL: media.GetUrl(), ObjectKey: media.GetObjectKey(), ContentType: media.GetContentType(), Format: media.GetFormat(),
		SizeBytes: media.GetSizeBytes(), Width: media.GetWidth(), Height: media.GetHeight(), Animated: media.GetAnimated(),
		FrameCount: media.GetFrameCount(), DurationMS: media.GetDurationMs(), SHA256: media.GetSha256(), Status: media.GetStatus(),
	}
}

// AvatarMediaToProto 返回已由 user owner 接受的媒体事实。
func AvatarMediaToProto(media userdomain.AvatarMedia) *userv1.UserAvatarMedia {
	return &userv1.UserAvatarMedia{
		Url: media.URL, ObjectKey: media.ObjectKey, ContentType: media.ContentType, Format: media.Format, SizeBytes: media.SizeBytes,
		Width: media.Width, Height: media.Height, Animated: media.Animated, FrameCount: media.FrameCount,
		DurationMs: media.DurationMS, Sha256: media.SHA256, Status: media.Status,
	}
}
