package vipconfig

import (
	"context"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/integration/walletclient"
	"hyapp-admin-server/internal/middleware"
	walletv1 "hyapp.local/api/proto/wallet/v1"

	"github.com/gin-gonic/gin"
)

func TestGetProgramReturnsCamelCaseCompleteConfigAndBenefits(t *testing.T) {
	wallet := &fakeVIPConfigWallet{
		programResponse: &walletv1.GetVipProgramConfigResponse{
			Config: &walletv1.VipProgramConfig{
				AppCode:                  "fami",
				ProgramType:              "tiered_privilege_v1",
				LevelCount:               9,
				SameLevelExpiryPolicy:    "extend_remaining",
				UpgradeExpiryPolicy:      "replace_from_now",
				DowngradePurchasePolicy:  "reject",
				BenefitInheritancePolicy: "target_only",
				GrantMode:                "trial_card",
				TrialCardEnabled:         true,
				Status:                   "active",
				ConfigVersion:            3,
				UpdatedByAdminId:         77,
				CreatedAtMs:              100,
				UpdatedAtMs:              200,
			},
			Benefits: []*walletv1.VipBenefit{{
				BenefitCode:    "anti_kick",
				Name:           "防踢",
				BenefitType:    "function",
				UnlockLevel:    9,
				Status:         "active",
				TrialEnabled:   true,
				ExecutionScope: "room",
				MetadataJson:   "{}",
				CreatedAtMs:    101,
				UpdatedAtMs:    201,
				Presentation: &walletv1.VipBenefitPresentation{
					Description: "普通房管无法踢出",
					PreviewItems: []*walletv1.VipBenefitPreviewItem{{
						PreviewId: "anti-kick", MediaType: "image", PreviewUrl: "https://cdn.example/anti-kick.png", CompositionType: "none",
					}},
				},
			}},
			ServerTimeMs: 300,
		},
	}
	recorder := serveVIPConfigRequest(t, wallet, http.MethodGet, "/admin/activity/vip-program", "")
	if recorder.Code != http.StatusOK {
		t.Fatalf("get program status = %d, body=%s", recorder.Code, recorder.Body.String())
	}
	body := recorder.Body.String()
	for _, fragment := range []string{
		`"programType":"tiered_privilege_v1"`,
		`"upgradeExpiryPolicy":"replace_from_now"`,
		`"updatedByAdminId":77`,
		`"benefitCode":"anti_kick"`,
		`"executionScope":"room"`,
		`"description":"普通房管无法踢出"`,
		`"compositionType":"none"`,
		`"serverTimeMs":300`,
	} {
		if !strings.Contains(body, fragment) {
			t.Fatalf("get program response missing %s: %s", fragment, body)
		}
	}
	if strings.Contains(body, "program_type") || strings.Contains(body, "benefit_code") {
		t.Fatalf("admin contract must stay camelCase: %s", body)
	}
	if wallet.getProgramRequest.GetAppCode() != "fami" || wallet.getProgramRequest.GetRequestId() != "vip-config-test" {
		t.Fatalf("get program request mismatch: %+v", wallet.getProgramRequest)
	}
}

func TestUpdateProgramUsesContextAppAndAuthenticatedOperator(t *testing.T) {
	wallet := &fakeVIPConfigWallet{updateProgramResponse: &walletv1.UpdateAdminVipProgramConfigResponse{
		Config:       &walletv1.VipProgramConfig{AppCode: "fami", ProgramType: "tiered_privilege_v1", LevelCount: 9, Status: "active", ConfigVersion: 4},
		ServerTimeMs: 400,
	}}
	body := `{"config":{"appCode":"lalu","programType":"tiered_privilege_v1","levelCount":9,"sameLevelExpiryPolicy":"extend_remaining","upgradeExpiryPolicy":"replace_from_now","downgradePurchasePolicy":"reject","benefitInheritancePolicy":"target_only","grantMode":"trial_card","trialCardEnabled":true,"status":"active","configVersion":999,"updatedByAdminId":999}}`
	recorder := serveVIPConfigRequest(t, wallet, http.MethodPut, "/admin/activity/vip-program", body)
	if recorder.Code != http.StatusOK {
		t.Fatalf("update program status = %d, body=%s", recorder.Code, recorder.Body.String())
	}
	req := wallet.updateProgramRequest
	if req.GetAppCode() != "fami" || req.GetOperatorUserId() != 77 {
		t.Fatalf("program scope/operator mismatch: %+v", req)
	}
	if req.GetConfig().GetAppCode() != "fami" || req.GetConfig().GetConfigVersion() != 0 || req.GetConfig().GetUpdatedByAdminId() != 0 {
		t.Fatalf("client-controlled program audit fields leaked through: %+v", req.GetConfig())
	}
	if !strings.Contains(recorder.Body.String(), `"configVersion":4`) || !strings.Contains(recorder.Body.String(), `"benefits":[]`) {
		t.Fatalf("update program response mismatch: %s", recorder.Body.String())
	}
}

func TestUpdateLevelsForwardsExplicitBenefitsAndReturnsProgram(t *testing.T) {
	wallet := &fakeVIPConfigWallet{updateLevelsResponse: &walletv1.UpdateAdminVipLevelsResponse{
		Levels: []*walletv1.VipLevel{{
			Level: 2, Name: "VIP2", Status: "active", DurationMs: 2_592_000_000,
			Benefits: []*walletv1.VipBenefit{{BenefitCode: "room_entry_notice", Name: "进房通知", Status: "active", ExecutionScope: "room", MetadataJson: "{}"}},
		}},
		ProgramConfig: &walletv1.VipProgramConfig{AppCode: "fami", ProgramType: "tiered_privilege_v1", LevelCount: 9},
		ServerTimeMs:  500,
	}}
	body := `{"levels":[{"level":2,"name":"VIP2","status":"active","priceCoin":2000,"durationMs":2592000000,"rewardResourceGroupId":22,"sortOrder":20,"requiredRechargeCoinAmount":123,"benefits":[{"benefitCode":"room_entry_notice","name":"进房通知","benefitType":"function","unlockLevel":2,"status":"active","trialEnabled":true,"resourceId":0,"resourceType":"","executionScope":"room","autoEquip":false,"sortOrder":10,"metadataJson":"{}","presentation":{"description":"当前房间进房动效","previewItems":[{"previewId":"entry-main","mediaType":"animation","animationUrl":"https://cdn.example/entry.svga","compositionType":"user_avatar_center","sortOrder":10}]}}]}]}`
	recorder := serveVIPConfigRequest(t, wallet, http.MethodPut, "/admin/activity/vip-levels", body)
	if recorder.Code != http.StatusOK {
		t.Fatalf("update levels status = %d, body=%s", recorder.Code, recorder.Body.String())
	}
	req := wallet.updateLevelsRequest
	if req.GetAppCode() != "fami" || req.GetOperatorUserId() != 77 || len(req.GetLevels()) != 1 {
		t.Fatalf("update levels request mismatch: %+v", req)
	}
	level := req.GetLevels()[0]
	if level.GetRequiredRechargeCoinAmount() != 0 || len(level.GetBenefits()) != 1 || level.GetBenefits()[0].GetBenefitCode() != "room_entry_notice" {
		t.Fatalf("explicit level benefits mismatch: %+v", level)
	}
	presentation := level.GetBenefits()[0].GetPresentation()
	if presentation.GetDescription() != "当前房间进房动效" || len(presentation.GetPreviewItems()) != 1 || presentation.GetPreviewItems()[0].GetCompositionType() != "user_avatar_center" {
		t.Fatalf("structured benefit presentation mismatch: %+v", presentation)
	}
	if !strings.Contains(recorder.Body.String(), `"programConfig":{"appCode":"fami","programType":"tiered_privilege_v1","levelCount":9`) {
		t.Fatalf("level response must include program config: %s", recorder.Body.String())
	}
}

func TestGrantTrialCardLeavesOptionalResourceUnsetAndUsesAuthenticatedOperator(t *testing.T) {
	wallet := &fakeVIPConfigWallet{grantTrialResponse: &walletv1.GrantVipTrialCardResponse{
		TrialCard:    &walletv1.VipTrialCard{TrialCardId: "trial-1", EntitlementId: "ent-1", UserId: 318705991371722752, Level: 5, DurationMs: 1_728_000_000, ExpiresAtMs: 2_000},
		State:        &walletv1.VipState{EffectiveSource: "none", EffectiveBenefits: []*walletv1.VipBenefit{}},
		ServerTimeMs: 1_000,
	}}
	body := `{"targetUserId":"318705991371722752","level":5,"durationMs":1728000000,"reason":"manual"}`
	recorder := serveVIPConfigRequest(t, wallet, http.MethodPost, "/admin/activity/vip-trial-card-grants", body)
	if recorder.Code != http.StatusCreated {
		t.Fatalf("grant trial card status = %d, body=%s", recorder.Code, recorder.Body.String())
	}
	req := wallet.grantTrialRequest
	if req.GetCommandId() != "admin_vip_trial_card_grant:vip-config-test" || req.GetAppCode() != "fami" || req.GetOperatorUserId() != 77 || req.GetGrantSource() != "admin_grant" {
		t.Fatalf("trial grant command context mismatch: %+v", req)
	}
	if req.ResourceId != nil || req.GetTargetUserId() != 318705991371722752 || req.GetDurationMs() != 1_728_000_000 {
		t.Fatalf("trial grant payload mismatch: %+v", req)
	}
	if !strings.Contains(recorder.Body.String(), `"trialCardId":"trial-1"`) || !strings.Contains(recorder.Body.String(), `"userId":"318705991371722752"`) {
		t.Fatalf("trial grant response mismatch: %s", recorder.Body.String())
	}
}

func TestExternalVIPGrantsUseManagerCenterOwnerPolicySource(t *testing.T) {
	wallet := &fakeVIPConfigWallet{
		grantVIPResponse: &walletv1.GrantVipResponse{TransactionId: "vip-external"},
		grantTrialResponse: &walletv1.GrantVipTrialCardResponse{
			TrialCard: &walletv1.VipTrialCard{TrialCardId: "trial-external"},
		},
	}
	gintest := func(path string, body string, handler gin.HandlerFunc) *httptest.ResponseRecorder {
		router := gin.New()
		router.Use(func(c *gin.Context) {
			ctx := appctx.WithContext(c.Request.Context(), "fami")
			ctx = appctx.WithExternalScope(ctx, appctx.ExternalScope{
				AccountID: 1, LinkedAppUserID: 77, RegionID: 9,
				OwnerUserIDs: []int64{318705991371722752},
			})
			c.Request = c.Request.WithContext(ctx)
			c.Set(middleware.ContextRequestID, "external-vip-test")
			c.Set(middleware.ContextUserID, uint(77))
			c.Next()
		})
		router.POST(path, handler)
		recorder := httptest.NewRecorder()
		request := httptest.NewRequest(http.MethodPost, path, strings.NewReader(body))
		request.Header.Set("Content-Type", "application/json")
		router.ServeHTTP(recorder, request)
		return recorder
	}
	handler := New(wallet, nil)
	trialRecorder := gintest("/external-trial", `{"targetUserId":"318705991371722752","level":5,"durationMs":86400000,"resourceId":99,"reason":"manual"}`, handler.GrantExternalVIPTrialCard)
	if trialRecorder.Code != http.StatusCreated || wallet.grantTrialRequest == nil || wallet.grantTrialRequest.GetGrantSource() != "manager_center" || wallet.grantTrialRequest.GetCommandId() != "external_vip_trial_card_grant:external-vip-test" {
		t.Fatalf("external trial source mismatch: status=%d request=%+v body=%s", trialRecorder.Code, wallet.grantTrialRequest, trialRecorder.Body.String())
	}
}

func TestMainAdminVIPGrantKeepsAdminSource(t *testing.T) {
	wallet := &fakeVIPConfigWallet{grantVIPResponse: &walletv1.GrantVipResponse{TransactionId: "vip-admin"}}
	handler := New(wallet, nil)
	router := gin.New()
	router.Use(func(c *gin.Context) {
		c.Request = c.Request.WithContext(appctx.WithContext(c.Request.Context(), "fami"))
		c.Set(middleware.ContextRequestID, "admin-vip-test")
		c.Set(middleware.ContextUserID, uint(77))
		c.Next()
	})
	router.POST("/admin-vip", handler.GrantVIP)
	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(http.MethodPost, "/admin-vip", strings.NewReader(`{"targetUserId":"318705991371722752","level":5,"reason":"manual"}`))
	request.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(recorder, request)
	if recorder.Code != http.StatusCreated || wallet.grantVIPRequest == nil || wallet.grantVIPRequest.GetGrantSource() != "admin_grant" || wallet.grantVIPRequest.GetCommandId() != "admin_vip_grant:admin-vip-test" {
		t.Fatalf("main admin VIP source changed: status=%d request=%+v body=%s", recorder.Code, wallet.grantVIPRequest, recorder.Body.String())
	}
}

func serveVIPConfigRequest(t *testing.T, wallet walletclient.Client, method string, path string, body string) *httptest.ResponseRecorder {
	t.Helper()
	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(func(c *gin.Context) {
		c.Request = c.Request.WithContext(appctx.WithContext(c.Request.Context(), "fami"))
		c.Set(middleware.ContextRequestID, "vip-config-test")
		c.Set(middleware.ContextUserID, uint(77))
		c.Set(middleware.ContextUsername, "vip-operator")
		c.Set(middleware.ContextPermissions, []string{"vip-config:view", "vip-config:update", "vip-config:grant"})
		c.Next()
	})
	RegisterRoutes(router.Group(""), New(wallet, nil))
	recorder := httptest.NewRecorder()
	request := httptest.NewRequest(method, path, strings.NewReader(body))
	if body != "" {
		request.Header.Set("Content-Type", "application/json")
	}
	router.ServeHTTP(recorder, request)
	return recorder
}

type fakeVIPConfigWallet struct {
	walletclient.Client
	programResponse       *walletv1.GetVipProgramConfigResponse
	updateProgramResponse *walletv1.UpdateAdminVipProgramConfigResponse
	updateLevelsResponse  *walletv1.UpdateAdminVipLevelsResponse
	grantTrialResponse    *walletv1.GrantVipTrialCardResponse
	grantVIPResponse      *walletv1.GrantVipResponse
	getProgramRequest     *walletv1.GetVipProgramConfigRequest
	updateProgramRequest  *walletv1.UpdateAdminVipProgramConfigRequest
	updateLevelsRequest   *walletv1.UpdateAdminVipLevelsRequest
	grantTrialRequest     *walletv1.GrantVipTrialCardRequest
	grantVIPRequest       *walletv1.GrantVipRequest
}

func (f *fakeVIPConfigWallet) GetVipProgramConfig(_ context.Context, req *walletv1.GetVipProgramConfigRequest) (*walletv1.GetVipProgramConfigResponse, error) {
	f.getProgramRequest = req
	return f.programResponse, nil
}

func (f *fakeVIPConfigWallet) UpdateAdminVipProgramConfig(_ context.Context, req *walletv1.UpdateAdminVipProgramConfigRequest) (*walletv1.UpdateAdminVipProgramConfigResponse, error) {
	f.updateProgramRequest = req
	return f.updateProgramResponse, nil
}

func (f *fakeVIPConfigWallet) UpdateAdminVipLevels(_ context.Context, req *walletv1.UpdateAdminVipLevelsRequest) (*walletv1.UpdateAdminVipLevelsResponse, error) {
	f.updateLevelsRequest = req
	return f.updateLevelsResponse, nil
}

func (f *fakeVIPConfigWallet) GrantVipTrialCard(_ context.Context, req *walletv1.GrantVipTrialCardRequest) (*walletv1.GrantVipTrialCardResponse, error) {
	f.grantTrialRequest = req
	return f.grantTrialResponse, nil
}

func (f *fakeVIPConfigWallet) GrantVip(_ context.Context, req *walletv1.GrantVipRequest) (*walletv1.GrantVipResponse, error) {
	f.grantVIPRequest = req
	return f.grantVIPResponse, nil
}
