package resource

import (
	"encoding/json"
	"strconv"
	"strings"
	"testing"
)

func TestParseGrantTargetUserIDAcceptsStringLongID(t *testing.T) {
	userID, err := parseGrantTargetUserID("318705991371722752")
	if err != nil {
		t.Fatalf("string long user id should be accepted: %v", err)
	}
	if userID != 318705991371722752 {
		t.Fatalf("user id mismatch: %d", userID)
	}
}

func TestParseGrantTargetUserIDKeepsLegacyNumber(t *testing.T) {
	userID, err := parseGrantTargetUserID(float64(163000))
	if err != nil {
		t.Fatalf("legacy numeric user id should be accepted: %v", err)
	}
	if userID != 163000 {
		t.Fatalf("user id mismatch: %d", userID)
	}
}

func TestApplyGrantOperatorsUsesSourceSpecificProfiles(t *testing.T) {
	grants := []grantDTO{
		{GrantID: "grant-admin", GrantSource: "admin", OperatorUserID: 7},
		{GrantID: "grant-manager", GrantSource: "manager_center", OperatorUserID: 9001},
	}

	applyGrantOperators(grants, map[uint]adminOperator{
		7: {UserID: 7, Username: "admin", Name: "后台管理员"},
	}, map[int64]managerOperator{
		9001: {UserID: 9001, DisplayUserID: "10086", Username: "经理 A", Avatar: "https://cdn.example/avatar.png"},
	})

	if grants[0].Operator == nil || grants[0].Operator.Source != "admin" || grants[0].Operator.Name != "后台管理员" || grants[0].Operator.Username != "admin" {
		t.Fatalf("admin operator mismatch: %+v", grants[0].Operator)
	}
	if grants[1].Operator == nil || grants[1].Operator.Source != "manager_center" || grants[1].Operator.DisplayUserID != "10086" || grants[1].Operator.Avatar == "" || grants[1].Operator.Username != "经理 A" {
		t.Fatalf("manager operator mismatch: %+v", grants[1].Operator)
	}
}

func TestApplyGrantTargetUsersKeepsFallbackUserID(t *testing.T) {
	grants := []grantDTO{
		{GrantID: "grant-with-user", TargetUserID: 1001},
		{GrantID: "grant-missing-user", TargetUserID: 1002},
	}

	applyGrantTargetUsers(grants, map[int64]grantUserDTO{
		1001: {UserID: 1001, DisplayUserID: "900001", Username: "玩家 A", Avatar: "https://cdn.example/user.png"},
	})

	if grants[0].TargetUser == nil || grants[0].TargetUser.DisplayUserID != "900001" || grants[0].TargetUser.Username != "玩家 A" {
		t.Fatalf("target user mismatch: %+v", grants[0].TargetUser)
	}
	if grants[1].TargetUser == nil || grants[1].TargetUser.UserID != 1002 || grants[1].TargetUser.DisplayUserID != "" {
		t.Fatalf("target fallback mismatch: %+v", grants[1].TargetUser)
	}
}

func TestEmojiPackMetadataDefaultsToAllRegions(t *testing.T) {
	metadata, err := emojiPackMetadataJSON([]int64{0, 1001}, "", "")
	if err != nil {
		t.Fatalf("metadata build failed: %v", err)
	}
	var payload emojiPackMetadataPayload
	if err := json.Unmarshal([]byte(metadata), &payload); err != nil {
		t.Fatalf("metadata json mismatch: %v", err)
	}
	if payload.RegionScope != "all" || len(payload.RegionIDs) != 0 || payload.Category != defaultEmojiPackCategory || payload.PricingType != emojiPackPricingFree {
		t.Fatalf("unexpected all-region metadata: %+v", payload)
	}
}

func TestEmojiPackMetadataKeepsLimitedRegions(t *testing.T) {
	metadata, err := emojiPackMetadataJSON([]int64{1001, 1001, 1002}, "会员", "paid")
	if err != nil {
		t.Fatalf("metadata build failed: %v", err)
	}
	payload := emojiPackMetadataFromJSON(metadata)
	if payload.RegionScope != "limited" || payload.Category != "会员" || payload.PricingType != emojiPackPricingPaid {
		t.Fatalf("unexpected limited-region metadata: %+v", payload)
	}
	if len(payload.RegionIDs) != 2 || payload.RegionIDs[0] != 1001 || payload.RegionIDs[1] != 1002 {
		t.Fatalf("unexpected region ids: %+v", payload.RegionIDs)
	}
}

func TestBadgeResourceMetadataUsesSelectedForm(t *testing.T) {
	metadata := resourceMetadataJSON(resourceTypeBadge, "long", "", "", "", "")
	payload := badgeMetadataPayload{}
	if err := json.Unmarshal([]byte(metadata), &payload); err != nil {
		t.Fatalf("badge metadata json mismatch: %v", err)
	}
	if payload.BadgeForm != badgeFormStrip || payload.BadgeKind != badgeKindNormal || payload.DefaultSlot != "profile_strip" {
		t.Fatalf("long badge metadata mismatch: %+v", payload)
	}
	if got := badgeFormFromMetadata(resourceMetadataJSON(resourceTypeBadge, "short", "", "", "", "")); got != badgeFormTile {
		t.Fatalf("short badge form mismatch: %s", got)
	}
}

func TestBadgeResourceMetadataMarksLevelBadge(t *testing.T) {
	metadata := resourceMetadataJSON(resourceTypeBadge, badgeFormStrip, "", badgeKindLevel, "wealth", "")
	payload := badgeMetadataPayload{}
	if err := json.Unmarshal([]byte(metadata), &payload); err != nil {
		t.Fatalf("badge metadata json mismatch: %v", err)
	}
	if payload.BadgeKind != badgeKindLevel || payload.LevelTrack != "wealth" || payload.BadgeForm != badgeFormStrip {
		t.Fatalf("level badge metadata mismatch: %+v", payload)
	}
}

func TestAvatarFrameResourceMetadataMarksLevelFrame(t *testing.T) {
	metadata := resourceMetadataJSON(resourceTypeAvatarFrame, "", mp4AlphaLayoutMetadataJSON("alpha_left_rgb_right", true), "", "game", avatarFrameKindLevel)
	payload := resourceMetadataPayload{}
	if err := json.Unmarshal([]byte(metadata), &payload); err != nil {
		t.Fatalf("avatar frame metadata json mismatch: %v", err)
	}
	if payload.AvatarFrameKind != avatarFrameKindLevel || payload.LevelTrack != "game" {
		t.Fatalf("level avatar frame metadata mismatch: %+v", payload)
	}
	if payload.AnimationFormat != resourceAnimationFormatMP4 || payload.MP4AlphaLayout == nil {
		t.Fatalf("level avatar frame should keep animation metadata: %+v", payload)
	}
}

func TestProfileCardResourceMetadataKeepsLayoutOnly(t *testing.T) {
	metadata := resourceMetadataJSON(resourceTypeProfileCard, "", `{
		"profile_card_layout": {
			"source_width": 1136,
			"source_height": 1680,
			"color_content_width": 750,
			"content_top": 117,
			"content_bottom": 1666,
			"content_height": 1550,
			"content_top_ratio": 0,
			"content_height_ratio": 0,
			"detect_version": 99
		},
		"debug": true
	}`, "", "", "")
	payload := profileCardMetadataPayload{}
	if err := json.Unmarshal([]byte(metadata), &payload); err != nil {
		t.Fatalf("profile card metadata json mismatch: %v", err)
	}
	if payload.ProfileCardLayout == nil {
		t.Fatalf("profile card layout should be kept")
	}
	if payload.ProfileCardLayout.ContentTop != 117 || payload.ProfileCardLayout.ContentHeight != 1550 {
		t.Fatalf("profile card layout mismatch: %+v", payload.ProfileCardLayout)
	}
	if payload.ProfileCardLayout.DetectVersion != 1 {
		t.Fatalf("profile card detect version should be normalized: %+v", payload.ProfileCardLayout)
	}
	if metadata == "{}" || strings.Contains(metadata, "debug") {
		t.Fatalf("profile card metadata should keep only layout: %s", metadata)
	}
}

func TestResourceMetadataKeepsMP4AlphaLayoutForAnimatedResources(t *testing.T) {
	for _, resourceType := range []string{"gift", "vehicle", "avatar_frame", "chat_bubble", "floating_screen", "mic_seat_animation"} {
		metadata := resourceMetadataJSON(resourceType, "", mp4AlphaLayoutMetadataJSON("alpha_left_rgb_right", true), "", "", "")
		payload := resourceMetadataPayload{}
		if err := json.Unmarshal([]byte(metadata), &payload); err != nil {
			t.Fatalf("%s mp4 metadata json mismatch: %v", resourceType, err)
		}
		if payload.AnimationFormat != resourceAnimationFormatMP4 || payload.MP4AlphaLayout == nil {
			t.Fatalf("%s mp4 metadata should be kept: %s", resourceType, metadata)
		}
		if payload.MP4AlphaLayout.AlphaLayout != mp4AlphaLayoutAlphaLeftRGBRight || payload.MP4AlphaLayout.RGBFrame[0] != 750 {
			t.Fatalf("%s mp4 layout mismatch: %+v", resourceType, payload.MP4AlphaLayout)
		}
	}
}

func TestResourceMetadataKeepsNormalMP4Layout(t *testing.T) {
	metadata := resourceMetadataJSON("gift", "", `{
		"animation_format": "mp4",
		"mp4_alpha_layout": {
			"alpha_layout": "normal",
			"video_w": 750,
			"video_h": 1334,
			"rgb_frame": [1, 2, 3, 4],
			"alpha_frame": null,
			"confirmed": true,
			"detect_version": 1
		}
	}`, "", "", "")
	payload := resourceMetadataPayload{}
	if err := json.Unmarshal([]byte(metadata), &payload); err != nil {
		t.Fatalf("normal mp4 metadata json mismatch: %v", err)
	}
	if payload.MP4AlphaLayout == nil || payload.MP4AlphaLayout.AlphaLayout != mp4AlphaLayoutNormal {
		t.Fatalf("normal mp4 metadata should be kept: %s", metadata)
	}
	if payload.MP4AlphaLayout.AlphaFrame != nil || payload.MP4AlphaLayout.RGBFrame[2] != 750 || payload.MP4AlphaLayout.RGBFrame[3] != 1334 {
		t.Fatalf("normal mp4 layout should be normalized to full frame: %+v", payload.MP4AlphaLayout)
	}
}

func TestProfileCardResourceMetadataKeepsLayoutAndMP4AlphaLayout(t *testing.T) {
	metadata := resourceMetadataJSON(resourceTypeProfileCard, "", `{
		"profile_card_layout": {
			"source_width": 1136,
			"source_height": 1680,
			"color_content_width": 750,
			"content_top": 117,
			"content_bottom": 1666,
			"content_height": 1550,
			"content_top_ratio": 0,
			"content_height_ratio": 0,
			"detect_version": 99
		},
		"mp4_alpha_layout": {
			"alpha_layout": "rgb_left_alpha_right",
			"video_w": 1472,
			"video_h": 1328,
			"rgb_frame": [0, 0, 736, 1328],
			"alpha_frame": [736, 0, 736, 1328],
			"confirmed": true,
			"detect_version": 1
		}
	}`, "", "", "")
	payload := resourceMetadataPayload{}
	if err := json.Unmarshal([]byte(metadata), &payload); err != nil {
		t.Fatalf("profile card mp4 metadata json mismatch: %v", err)
	}
	if payload.ProfileCardLayout == nil || payload.ProfileCardLayout.ContentTop != 117 {
		t.Fatalf("profile card layout should be kept: %+v", payload.ProfileCardLayout)
	}
	if payload.MP4AlphaLayout == nil || payload.MP4AlphaLayout.AlphaLayout != mp4AlphaLayoutRGBLeftAlphaRight {
		t.Fatalf("profile card mp4 layout should be kept: %+v", payload.MP4AlphaLayout)
	}
}

func TestValidateResourceMetadataRejectsInvalidMP4AlphaLayout(t *testing.T) {
	cases := []struct {
		name string
		req  resourceRequest
	}{
		{
			name: "unconfirmed",
			req: resourceRequest{
				ResourceType: "gift",
				MetadataJSON: mp4AlphaLayoutMetadataJSON("alpha_left_rgb_right", false),
			},
		},
		{
			name: "bad rect",
			req: resourceRequest{
				ResourceType: "gift",
				MetadataJSON: `{
					"mp4_alpha_layout": {
						"alpha_layout": "alpha_left_rgb_right",
						"video_w": 1500,
						"video_h": 1334,
						"rgb_frame": [750, 0, 800, 1334],
						"alpha_frame": [0, 0, 750, 1334],
						"confirmed": true,
						"detect_version": 1
					}
				}`,
			},
		},
		{
			name: "unsupported type",
			req: resourceRequest{
				ResourceType: resourceTypeBadge,
				MetadataJSON: mp4AlphaLayoutMetadataJSON("alpha_left_rgb_right", true),
			},
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			if err := validateResourceMetadata(tc.req); err == nil {
				t.Fatalf("invalid mp4 metadata should be rejected")
			}
		})
	}
	if err := validateResourceMetadata(resourceRequest{ResourceType: "gift", MetadataJSON: mp4AlphaLayoutMetadataJSON("alpha_left_rgb_right", true)}); err != nil {
		t.Fatalf("valid mp4 metadata rejected: %v", err)
	}
}

func TestProfileCardResourceMetadataRejectsInvalidLayout(t *testing.T) {
	metadata := resourceMetadataJSON(resourceTypeProfileCard, "", `{
		"profile_card_layout": {
			"source_width": 1136,
			"source_height": 1680,
			"content_top": 117,
			"content_bottom": 1666,
			"content_height": 1
		}
	}`, "", "", "")
	if metadata != "{}" {
		t.Fatalf("invalid profile card layout should be dropped: %s", metadata)
	}
}

func TestValidateBadgeResourceRequiresBadgeForm(t *testing.T) {
	if err := validateResourceBadgeForm(resourceRequest{ResourceType: resourceTypeBadge}); err == nil {
		t.Fatalf("badge form should be required")
	}
	if err := validateResourceBadgeForm(resourceRequest{ResourceType: resourceTypeBadge, BadgeForm: badgeFormTile}); err != nil {
		t.Fatalf("valid badge form rejected: %v", err)
	}
	if err := validateResourceBadgeForm(resourceRequest{ResourceType: resourceTypeBadge, BadgeForm: badgeFormStrip, BadgeKind: badgeKindLevel}); err == nil {
		t.Fatalf("level badge should require level track")
	}
	if err := validateResourceBadgeForm(resourceRequest{ResourceType: resourceTypeBadge, BadgeForm: badgeFormStrip, BadgeKind: badgeKindLevel, LevelTrack: "wealth"}); err != nil {
		t.Fatalf("valid level badge rejected: %v", err)
	}
	if err := validateResourceBadgeForm(resourceRequest{ResourceType: "gift", BadgeForm: ""}); err != nil {
		t.Fatalf("non-badge resource should not require badge form: %v", err)
	}
}

func TestValidateAvatarFrameResourceRequiresLevelTrack(t *testing.T) {
	if err := validateResourceAvatarFrameKind(resourceRequest{ResourceType: resourceTypeAvatarFrame}); err != nil {
		t.Fatalf("normal avatar frame should be default: %v", err)
	}
	if err := validateResourceAvatarFrameKind(resourceRequest{ResourceType: resourceTypeAvatarFrame, AvatarFrameKind: avatarFrameKindLevel}); err == nil {
		t.Fatalf("level avatar frame should require level track")
	}
	if err := validateResourceAvatarFrameKind(resourceRequest{ResourceType: resourceTypeAvatarFrame, AvatarFrameKind: avatarFrameKindLevel, LevelTrack: "charm"}); err != nil {
		t.Fatalf("valid level avatar frame rejected: %v", err)
	}
	if err := validateResourceAvatarFrameKind(resourceRequest{ResourceType: "gift", AvatarFrameKind: "bad"}); err != nil {
		t.Fatalf("non-avatar-frame resource should not require avatar frame kind: %v", err)
	}
}

func mp4AlphaLayoutMetadataJSON(alphaLayout string, confirmed bool) string {
	return `{
		"animation_format": "mp4",
		"mp4_alpha_layout": {
			"alpha_layout": "` + alphaLayout + `",
			"video_w": 1500,
			"video_h": 1334,
			"rgb_frame": [750, 0, 750, 1334],
			"alpha_frame": [0, 0, 750, 1334],
			"confirmed": ` + strconv.FormatBool(confirmed) + `,
			"detect_version": 1
		},
		"debug": true
	}`
}
