package externaladmin

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

	"hyapp-admin-server/internal/appctx"
	"hyapp-admin-server/internal/config"
	"hyapp-admin-server/internal/integration/userclient"
	"hyapp-admin-server/internal/integration/walletclient"
	adminmiddleware "hyapp-admin-server/internal/middleware"
	"hyapp-admin-server/internal/modules/appuser"
	"hyapp-admin-server/internal/modules/hostorg"
	"hyapp-admin-server/internal/modules/vipconfig"
	walletv1 "hyapp.local/api/proto/wallet/v1"

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

func TestExternalBusinessRoutesKeepIndependentPermissionBoundaries(t *testing.T) {
	gin.SetMode(gin.TestMode)
	engine := gin.New()
	engine.Use(func(c *gin.Context) {
		// Each request gets exactly one product capability. Allowed reads reach an
		// intentionally unconfigured handler and return 500; denied routes must stop
		// at the permission middleware with 403 before any dependency is touched.
		c.Set(adminmiddleware.ContextPermissions, []string{c.GetHeader("X-Test-Permission")})
		c.Next()
	})
	registerBusinessWhitelist(engine.Group(""), &Handler{}, BusinessHandlers{
		AppUser: appuser.New(nil, nil, nil, nil, nil, config.Config{}, nil),
		HostOrg: hostorg.New(nil, nil, nil, nil, nil, nil),
	})

	tests := []struct {
		name       string
		method     string
		path       string
		permission string
		allowed    bool
	}{
		{name: "external SuperAdmin list accepts its capability", method: http.MethodGet, path: "/admin/external-super-admins", permission: "external-super-admin:list", allowed: true},
		{name: "external SuperAdmin list rejects retired capability", method: http.MethodGet, path: "/admin/external-super-admins", permission: "super-admin:list"},
		{name: "user update can read support list", method: http.MethodGet, path: "/app/users", permission: "user:update", allowed: true},
		{name: "user update cannot ban", method: http.MethodPost, path: "/app/users/9001/ban", permission: "user:update"},
		{name: "user unban can read ban support list", method: http.MethodGet, path: "/app/users/bans", permission: "user:unban", allowed: true},
	}
	for _, testCase := range tests {
		t.Run(testCase.name, func(t *testing.T) {
			request := httptest.NewRequest(testCase.method, testCase.path, nil)
			request.Header.Set("X-Test-Permission", testCase.permission)
			recorder := httptest.NewRecorder()
			engine.ServeHTTP(recorder, request)
			if testCase.allowed && recorder.Code == http.StatusForbidden {
				t.Fatalf("permission %s was rejected for %s %s: %s", testCase.permission, testCase.method, testCase.path, recorder.Body.String())
			}
			if !testCase.allowed && recorder.Code != http.StatusForbidden {
				t.Fatalf("permission %s unexpectedly reached %s %s: status=%d body=%s", testCase.permission, testCase.method, testCase.path, recorder.Code, recorder.Body.String())
			}
		})
	}
	for _, retiredPath := range []string{"/admin/managers", "/admin/bd-leaders", "/admin/my-team/agencies"} {
		request := httptest.NewRequest(http.MethodGet, retiredPath, nil)
		request.Header.Set("X-Test-Permission", "external-super-admin:list")
		recorder := httptest.NewRecorder()
		engine.ServeHTTP(recorder, request)
		if recorder.Code != http.StatusNotFound {
			t.Fatalf("retired external route %s status=%d body=%s", retiredPath, recorder.Code, recorder.Body.String())
		}
	}
}

func TestExternalVIPRoutesExposeOnlyManagerCenterTrialCards(t *testing.T) {
	gin.SetMode(gin.TestMode)
	wallet := &externalVIPRouteWallet{}
	engine := gin.New()
	engine.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{77}})
		c.Request = c.Request.WithContext(ctx)
		c.Set(contextScope, RequestScope{AccountID: 1, LinkedAppUserID: 77, RegionID: 9, OwnerUserIDs: []int64{77}})
		c.Set(adminmiddleware.ContextPermissions, []string{"user-level:grant"})
		c.Set(adminmiddleware.ContextRequestID, "external-vip-route-test")
		c.Set(adminmiddleware.ContextUserID, uint(77))
		c.Next()
	})
	teamClient := &externalVIPRouteUserClient{}
	registerBusinessWhitelist(engine.Group(""), &Handler{}, BusinessHandlers{
		AppUser:   appuser.New(teamClient, nil, nil, nil, nil, config.Config{}, nil),
		VIPConfig: vipconfig.New(wallet, nil),
	})

	vipRequest := httptest.NewRequest(http.MethodPost, "/admin/activity/vip-grants", strings.NewReader(`{"targetUserId":"9001","level":5,"reason":"manual"}`))
	vipRequest.Header.Set("Content-Type", "application/json")
	vipRecorder := httptest.NewRecorder()
	engine.ServeHTTP(vipRecorder, vipRequest)
	if vipRecorder.Code != http.StatusNotFound || wallet.vipRequest != nil {
		t.Fatalf("direct external VIP route must be absent: status=%d request=%+v body=%s", vipRecorder.Code, wallet.vipRequest, vipRecorder.Body.String())
	}

	trialRequest := httptest.NewRequest(http.MethodPost, "/admin/activity/vip-trial-card-grants", strings.NewReader(`{"targetUserId":"9001","level":5,"durationMs":86400000,"resourceId":99,"reason":"manual"}`))
	trialRequest.Header.Set("Content-Type", "application/json")
	trialRecorder := httptest.NewRecorder()
	engine.ServeHTTP(trialRecorder, trialRequest)
	if trialRecorder.Code != http.StatusCreated || wallet.trialRequest == nil || wallet.trialRequest.GetGrantSource() != "manager_center" {
		t.Fatalf("external trial route source: status=%d request=%+v body=%s", trialRecorder.Code, wallet.trialRequest, trialRecorder.Body.String())
	}
}

type externalVIPRouteUserClient struct {
	userclient.Client
}

func (client *externalVIPRouteUserClient) ListExternalTeamUsers(_ context.Context, request userclient.ListExternalTeamUsersRequest) (*userclient.ListExternalTeamUsersResult, error) {
	return &userclient.ListExternalTeamUsersResult{
		Users: []userclient.ExternalTeamUser{{UserID: 9001, RegionID: request.RegionID, Status: request.Status}},
		Total: 1, Page: request.Page, PageSize: request.PageSize,
	}, nil
}

func (client *externalVIPRouteUserClient) ExternalInviteBD(context.Context, userclient.ExternalInviteBDRequest) (*userclient.ExternalRoleInvitation, error) {
	return nil, nil
}
func (client *externalVIPRouteUserClient) ExternalInviteAgency(context.Context, userclient.ExternalInviteAgencyRequest) (*userclient.ExternalRoleInvitation, error) {
	return nil, nil
}
func (client *externalVIPRouteUserClient) ExternalInviteHost(context.Context, userclient.ExternalInviteHostRequest) (*userclient.ExternalRoleInvitation, error) {
	return nil, nil
}
func (client *externalVIPRouteUserClient) SearchExternalInvitationCandidate(context.Context, userclient.SearchExternalInvitationCandidateRequest) (*userclient.ExternalTeamUser, error) {
	return nil, nil
}

type externalVIPRouteWallet struct {
	walletclient.Client
	vipRequest   *walletv1.GrantVipRequest
	trialRequest *walletv1.GrantVipTrialCardRequest
}

func (wallet *externalVIPRouteWallet) GrantVip(_ context.Context, request *walletv1.GrantVipRequest) (*walletv1.GrantVipResponse, error) {
	wallet.vipRequest = request
	return &walletv1.GrantVipResponse{TransactionId: "vip-route"}, nil
}

func (wallet *externalVIPRouteWallet) GrantVipTrialCard(_ context.Context, request *walletv1.GrantVipTrialCardRequest) (*walletv1.GrantVipTrialCardResponse, error) {
	wallet.trialRequest = request
	return &walletv1.GrantVipTrialCardResponse{TrialCard: &walletv1.VipTrialCard{TrialCardId: "trial-route"}}, nil
}
