package http

import (
	"encoding/json"
	"errors"
	"hyapp/services/gateway-service/internal/transport/http/httpkit"
	"net/http"
	"net/http/httptest"
	"testing"

	userv1 "hyapp.local/api/proto/user/v1"
	walletv1 "hyapp.local/api/proto/wallet/v1"
	"hyapp/pkg/xerr"
	"hyapp/services/gateway-service/internal/auth"
)

func TestGetMyOverviewComposesProfileWalletVIPStatsRolesAndEntries(t *testing.T) {
	profileClient := &fakeUserProfileClient{
		regionID: 10,
		usersByID: map[int64]*userv1.User{42: {
			UserId:           42,
			DisplayUserId:    "100001",
			Username:         "hy",
			Gender:           "female",
			RegionId:         10,
			ProfileCompleted: true,
			OnboardingStatus: "completed",
			CreatedAtMs:      1_770_000_000_000,
			ProfileBgImg:     "https://cdn.example/overview-profile-bg.png",
		}},
		statsResp: &userv1.GetMyProfileStatsResponse{Stats: &userv1.UserProfileStats{
			UserId:         42,
			VisitorsCount:  1234,
			FollowingCount: 2345,
			FollowersCount: 3000,
			FriendsCount:   3456,
			UpdatedAtMs:    7000,
		}},
	}
	walletClient := &fakeWalletClient{
		valueSummaryResp: &walletv1.GetWalletValueSummaryResponse{Summary: &walletv1.WalletValueSummary{CoinAmount: 12000, UpdatedAtMs: 8000}},
		myVipResp: &walletv1.GetMyVipResponse{
			Vip: &walletv1.UserVip{UserId: 42, Level: 3, Name: "VIP3", Active: true, StartedAtMs: 9000, ExpiresAtMs: 19000},
			State: &walletv1.VipState{UserSettings: &walletv1.VipUserSettings{
				AppCode: "lalu", UserId: 42, RoomEntryNoticeEnabled: true, OnlineGlobalNoticeEnabled: false,
				HideProfileDataEnabled: false, AnonymousProfileVisitEnabled: true, LeaderboardInvisibleEnabled: false,
				UpdatedAtMs: 8500,
			}},
		},
	}
	hostClient := &fakeUserHostClient{
		roleSummary: &userv1.UserRoleSummary{
			UserId:                 42,
			IsHost:                 true,
			IsAgency:               true,
			IsManager:              true,
			IsBd:                   true,
			IsBdLeader:             true,
			IsCoinSeller:           true,
			HostStatus:             "active",
			AgencyId:               7001,
			BdId:                   42,
			BdStatus:               "active",
			CoinSellerStatus:       "active",
			PendingRoleInvitations: 4,
		},
	}
	messageClient := &fakeMessageInboxClient{}
	handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient)
	handler.SetWalletClient(walletClient)
	handler.SetUserHostClient(hostClient)
	handler.SetMessageInboxClient(messageClient)

	data := performOverviewRequest(t, handler, "req-overview")

	if profileClient.lastGet == nil || profileClient.lastGet.GetUserId() != 42 || profileClient.lastGet.GetMeta().GetAppCode() != "lalu" {
		t.Fatalf("profile request mismatch: %+v", profileClient.lastGet)
	}
	if profileClient.lastStats == nil || profileClient.lastStats.GetUserId() != 42 || profileClient.lastStats.GetMeta().GetAppCode() != "lalu" {
		t.Fatalf("profile stats request mismatch: %+v", profileClient.lastStats)
	}
	if walletClient.lastValueSummary == nil || walletClient.lastValueSummary.GetUserId() != 42 || walletClient.lastValueSummary.GetAppCode() != "lalu" {
		t.Fatalf("wallet value summary request mismatch: %+v", walletClient.lastValueSummary)
	}
	if walletClient.lastMyVip == nil || walletClient.lastMyVip.GetUserId() != 42 || walletClient.lastMyVip.GetAppCode() != "lalu" {
		t.Fatalf("vip request mismatch: %+v", walletClient.lastMyVip)
	}
	if hostClient.lastRoleSummary == nil || hostClient.lastRoleSummary.GetUserId() != 42 || hostClient.lastRoleSummary.GetMeta().GetAppCode() != "lalu" {
		t.Fatalf("role summary request mismatch: %+v", hostClient.lastRoleSummary)
	}
	if messageClient.lastTabs != nil {
		t.Fatalf("my overview must not request message tabs: %+v", messageClient.lastTabs)
	}

	profile := data["profile"].(map[string]any)
	if profile["user_id"] != "42" || profile["username"] != "hy" || profile["region_id"] != float64(10) || profile["created_at_ms"] != float64(1_770_000_000_000) || profile["profile_bg_img"] != "https://cdn.example/overview-profile-bg.png" {
		t.Fatalf("profile data mismatch: %+v", profile)
	}
	stats := data["profile_stats"].(map[string]any)
	if stats["visitors_count"] != float64(1234) || stats["following_count"] != float64(2345) || stats["followers_count"] != float64(3000) || stats["friends_count"] != float64(3456) || stats["updated_at_ms"] != float64(7000) {
		t.Fatalf("profile stats mismatch: %+v", stats)
	}
	roles := data["roles"].(map[string]any)
	if roles["is_host"] != true || roles["is_agency"] != true || roles["is_manager"] != true || roles["is_bd"] != true || roles["is_bd_leader"] != true || roles["is_coin_seller"] != true || roles["agency_id"] != float64(7001) {
		t.Fatalf("roles data mismatch: %+v", roles)
	}
	wallet := data["wallet"].(map[string]any)
	if wallet["coin_amount"] != float64(12000) || wallet["updated_at_ms"] != float64(8000) {
		t.Fatalf("wallet value summary mismatch: %+v", wallet)
	}
	vip := data["vip"].(map[string]any)
	if vip["level"] != float64(3) || vip["name"] != "VIP3" || vip["active"] != true {
		t.Fatalf("vip summary mismatch: %+v", vip)
	}
	settings := vip["user_settings"].(map[string]any)
	if settings["app_code"] != "lalu" || settings["user_id"] != float64(42) ||
		settings["room_entry_notice_enabled"] != true || settings["online_global_notice_enabled"] != false ||
		settings["hide_profile_data_enabled"] != false || settings["anonymous_profile_visit_enabled"] != true ||
		settings["leaderboard_invisible_enabled"] != false || settings["updated_at_ms"] != float64(8500) {
		t.Fatalf("vip user settings mismatch: %+v", settings)
	}
	if _, ok := data["invite"]; ok {
		t.Fatalf("my overview must not include invite: %+v", data["invite"])
	}
	if _, ok := data["badges"]; ok {
		t.Fatalf("my overview must not include message badges: %+v", data["badges"])
	}

	entries := data["entries"].([]any)
	if !overviewEntryVisible(entries, "store") ||
		!overviewEntryVisible(entries, "bag") ||
		!overviewEntryVisible(entries, "level") ||
		!overviewEntryVisible(entries, "task") ||
		!overviewEntryVisible(entries, "host_center") ||
		!overviewEntryVisible(entries, "agency_center") ||
		!overviewEntryVisible(entries, "manager_center") ||
		!overviewEntryVisible(entries, "bd_center") ||
		!overviewEntryVisible(entries, "superadmin_center") ||
		!overviewEntryVisible(entries, "coin_seller_center") ||
		!overviewEntryVisible(entries, "settings") ||
		!overviewEntryVisible(entries, "language") ||
		overviewEntryVisible(entries, "apply_host") {
		t.Fatalf("entry visibility mismatch: %+v", entries)
	}
	if route := overviewEntryRoute(entries, "superadmin_center"); route != "/superadmin-center" {
		t.Fatalf("superadmin center route mismatch: got %q entries=%+v", route, entries)
	}
	if data["server_time_ms"].(float64) <= 0 {
		t.Fatalf("missing server_time_ms: %+v", data)
	}
}

func TestGetMyOverviewProjectsOrdinaryUserEntriesAndZeroWallet(t *testing.T) {
	handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{})
	handler.SetWalletClient(&fakeWalletClient{})
	handler.SetUserHostClient(&fakeUserHostClient{})
	handler.SetMessageInboxClient(&fakeMessageInboxClient{})

	data := performOverviewRequest(t, handler, "req-overview-empty-wallet")

	wallet := data["wallet"].(map[string]any)
	if wallet["coin_amount"] != float64(0) || wallet["updated_at_ms"] != float64(0) {
		t.Fatalf("missing wallet summary should be zero projection: %+v", wallet)
	}
	stats := data["profile_stats"].(map[string]any)
	if stats["visitors_count"] != float64(0) || stats["following_count"] != float64(0) || stats["followers_count"] != float64(0) || stats["friends_count"] != float64(0) {
		t.Fatalf("missing stats should be zero projection: %+v", stats)
	}
	roles := data["roles"].(map[string]any)
	if roles["is_host"] != false || roles["is_manager"] != false || roles["is_bd"] != false || roles["is_coin_seller"] != false {
		t.Fatalf("ordinary roles mismatch: %+v", roles)
	}
	vip := data["vip"].(map[string]any)
	settings := vip["user_settings"].(map[string]any)
	if settings["app_code"] != "lalu" || settings["user_id"] != float64(42) ||
		settings["room_entry_notice_enabled"] != true || settings["online_global_notice_enabled"] != true ||
		settings["hide_profile_data_enabled"] != false || settings["anonymous_profile_visit_enabled"] != false ||
		settings["leaderboard_invisible_enabled"] != false {
		t.Fatalf("missing wallet state must use notice-on privacy-off defaults: %+v", settings)
	}
	entries := data["entries"].([]any)
	if !overviewEntryVisible(entries, "apply_host") || overviewEntryVisible(entries, "host_center") {
		t.Fatalf("ordinary entries mismatch: %+v", entries)
	}
}

func TestGetMyOverviewHidesAgencyCenterForOrdinaryHost(t *testing.T) {
	handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{})
	handler.SetWalletClient(&fakeWalletClient{})
	handler.SetUserHostClient(&fakeUserHostClient{roleSummary: &userv1.UserRoleSummary{
		UserId:     42,
		IsHost:     true,
		IsAgency:   false,
		HostStatus: "active",
	}})
	handler.SetMessageInboxClient(&fakeMessageInboxClient{})

	data := performOverviewRequest(t, handler, "req-overview-host-only")

	roles := data["roles"].(map[string]any)
	if roles["is_host"] != true || roles["is_agency"] != false {
		t.Fatalf("ordinary host roles mismatch: %+v", roles)
	}
	entries := data["entries"].([]any)
	if !overviewEntryVisible(entries, "host_center") || overviewEntryVisible(entries, "agency_center") || overviewEntryVisible(entries, "apply_host") {
		t.Fatalf("ordinary host must only expose host center: %+v", entries)
	}
}

func TestGetMyOverviewDegradesOptionalUpstreamFailures(t *testing.T) {
	handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{statsErr: errors.New("stats down")})
	handler.SetWalletClient(&fakeWalletClient{err: errors.New("wallet down")})
	handler.SetUserHostClient(&fakeUserHostClient{roleSummaryErr: xerr.ToGRPCError(xerr.New(xerr.Internal, "role service down"))})
	handler.SetMessageInboxClient(&fakeMessageInboxClient{})

	data := performOverviewRequest(t, handler, "req-overview-degraded")

	wallet := data["wallet"].(map[string]any)
	if wallet["unavailable"] != true || wallet["coin_amount"] != float64(0) {
		t.Fatalf("wallet degradation mismatch: %+v", wallet)
	}
	stats := data["profile_stats"].(map[string]any)
	if stats["unavailable"] != true || stats["visitors_count"] != float64(0) || stats["following_count"] != float64(0) || stats["followers_count"] != float64(0) || stats["friends_count"] != float64(0) {
		t.Fatalf("profile stats degradation mismatch: %+v", stats)
	}
	roles := data["roles"].(map[string]any)
	if roles["unavailable"] != true || roles["is_host"] != false || roles["is_manager"] != false || roles["is_bd"] != false || roles["is_coin_seller"] != false {
		t.Fatalf("roles degradation mismatch: %+v", roles)
	}
	entries := data["entries"].([]any)
	if overviewEntryVisible(entries, "host_center") || overviewEntryVisible(entries, "manager_center") || overviewEntryVisible(entries, "bd_center") || overviewEntryVisible(entries, "coin_seller_center") {
		t.Fatalf("degraded roles must not expose privileged entries: %+v", entries)
	}
}

func TestGetMyOverviewFailsWhenProfileFails(t *testing.T) {
	handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{
		getErr: xerr.ToGRPCError(xerr.New(xerr.NotFound, "user not found")),
	})
	handler.SetWalletClient(&fakeWalletClient{})
	handler.SetUserHostClient(&fakeUserHostClient{})
	router := handler.Routes(auth.NewVerifier("secret"))
	request := httptest.NewRequest(http.MethodGet, "/api/v1/users/me/overview", nil)
	request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
	request.Header.Set("X-Request-ID", "req-overview-profile-fail")
	recorder := httptest.NewRecorder()

	router.ServeHTTP(recorder, request)

	assertEnvelope(t, recorder, http.StatusNotFound, string(xerr.NotFound), "req-overview-profile-fail")
}

func performOverviewRequest(t *testing.T, handler *Handler, requestID string) map[string]any {
	t.Helper()

	router := handler.Routes(auth.NewVerifier("secret"))
	request := httptest.NewRequest(http.MethodGet, "/api/v1/users/me/overview", nil)
	request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
	request.Header.Set("X-Request-ID", requestID)
	recorder := httptest.NewRecorder()

	router.ServeHTTP(recorder, request)

	if recorder.Code != http.StatusOK {
		t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
	}
	var response httpkit.ResponseEnvelope
	if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
		t.Fatalf("decode overview response failed: %v", err)
	}
	data, ok := response.Data.(map[string]any)
	if response.Code != httpkit.CodeOK || !ok || response.RequestID != assertGeneratedRequestID(t, recorder, requestID) {
		t.Fatalf("overview envelope mismatch: %+v", response)
	}
	return data
}

func overviewEntryVisible(entries []any, key string) bool {
	for _, entry := range entries {
		item, ok := entry.(map[string]any)
		if ok && item["key"] == key {
			visible, _ := item["visible"].(bool)
			return visible
		}
	}
	return false
}

func overviewEntryRoute(entries []any, key string) string {
	for _, entry := range entries {
		item, ok := entry.(map[string]any)
		if ok && item["key"] == key {
			route, _ := item["route"].(string)
			return route
		}
	}
	return ""
}
