package opscenter

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

	"hyapp-admin-server/internal/integration/luckygiftadmin"
	"hyapp-admin-server/internal/middleware"
	luckygiftmodule "hyapp-admin-server/internal/modules/luckygift"
	luckygiftv1 "hyapp.local/api/proto/luckygift/v1"

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

type poolPermissionClient struct {
	luckygiftadmin.Client
}

func (poolPermissionClient) AdjustLuckyGiftPoolBalance(_ context.Context, req *luckygiftv1.AdjustLuckyGiftPoolBalanceRequest) (*luckygiftv1.AdjustLuckyGiftPoolBalanceResponse, error) {
	return &luckygiftv1.AdjustLuckyGiftPoolBalanceResponse{
		Adjustment: &luckygiftv1.LuckyGiftPoolAdjustment{
			AdjustmentId: req.GetAdjustmentId(), AppCode: req.GetMeta().GetAppCode(), PoolId: req.GetPoolId(),
			StrategyVersion: req.GetStrategyVersion(), Direction: req.GetDirection(), AmountCoins: req.GetAmountCoins(),
		},
		Pool: &luckygiftv1.LuckyGiftPoolBalance{
			AppCode: req.GetMeta().GetAppCode(), PoolId: req.GetPoolId(), StrategyVersion: req.GetStrategyVersion(), Materialized: true,
		},
	}, nil
}

func TestRegisterRoutesExposesStrategyPoolAdjustmentEndpoints(t *testing.T) {
	gin.SetMode(gin.TestMode)
	engine := gin.New()
	RegisterRoutes(engine.Group("/api/v1"), New(nil, luckygiftmodule.New(poolPermissionClient{}, 0, nil)))
	routes := make(map[string]struct{})
	for _, route := range engine.Routes() {
		routes[route.Method+" "+route.Path] = struct{}{}
	}
	for _, expected := range []string{
		"POST /api/v1/admin/ops-center/lucky-gifts/pools/credit",
		"POST /api/v1/admin/ops-center/lucky-gifts/pools/debit",
		"GET /api/v1/admin/ops-center/lucky-gifts/experiments",
		"POST /api/v1/admin/ops-center/lucky-gifts/experiments",
		"PATCH /api/v1/admin/ops-center/lucky-gifts/experiments/:experimentId",
		"PUT /api/v1/admin/ops-center/lucky-gifts/experiments/:experimentId/overrides",
		"POST /api/v1/admin/ops-center/lucky-gifts/experiments/:experimentId/end",
	} {
		if _, ok := routes[expected]; !ok {
			t.Fatalf("missing route %s; actual=%v", expected, routes)
		}
	}
}

type experimentPermissionClient struct {
	luckygiftadmin.Client
}

func (experimentPermissionClient) ListLuckyGiftExperiments(_ context.Context, _ *luckygiftv1.ListLuckyGiftExperimentsRequest) (*luckygiftv1.ListLuckyGiftExperimentsResponse, error) {
	return &luckygiftv1.ListLuckyGiftExperimentsResponse{}, nil
}

func (experimentPermissionClient) CreateLuckyGiftExperiment(_ context.Context, req *luckygiftv1.CreateLuckyGiftExperimentRequest) (*luckygiftv1.CreateLuckyGiftExperimentResponse, error) {
	return &luckygiftv1.CreateLuckyGiftExperimentResponse{
		Experiment:      &luckygiftv1.LuckyGiftExperiment{ExperimentId: "lucky_exp_route", Name: req.GetName()},
		TreatmentConfig: req.GetTreatmentConfig(),
	}, nil
}

func (experimentPermissionClient) UpdateLuckyGiftExperiment(_ context.Context, req *luckygiftv1.UpdateLuckyGiftExperimentRequest) (*luckygiftv1.UpdateLuckyGiftExperimentResponse, error) {
	return &luckygiftv1.UpdateLuckyGiftExperimentResponse{
		Experiment: &luckygiftv1.LuckyGiftExperiment{ExperimentId: req.GetExperimentId()},
	}, nil
}

func TestExperimentRoutesRequireExperimentPermission(t *testing.T) {
	gin.SetMode(gin.TestMode)
	tests := []struct {
		name       string
		method     string
		target     string
		body       string
		permission string
		wantStatus int
	}{
		{
			name: "list allowed with view", method: http.MethodGet,
			target: "/api/v1/admin/ops-center/lucky-gifts/experiments?app_code=lalu",
			body:   "", permission: "lucky-gift:view", wantStatus: http.StatusOK,
		},
		{
			name: "create allowed with experiment", method: http.MethodPost,
			target: "/api/v1/admin/ops-center/lucky-gifts/experiments?app_code=lalu",
			body:   `{"name":"exp","treatment_traffic_percent":5,"config":{"pool_id":"lucky"}}`,
			permission: "lucky-gift:experiment", wantStatus: http.StatusOK,
		},
		{
			name: "create rejects update permission", method: http.MethodPost,
			target: "/api/v1/admin/ops-center/lucky-gifts/experiments?app_code=lalu",
			body:   `{"name":"exp","treatment_traffic_percent":5,"config":{"pool_id":"lucky"}}`,
			permission: "lucky-gift:update", wantStatus: http.StatusForbidden,
		},
		{
			name: "patch allowed with experiment", method: http.MethodPatch,
			target: "/api/v1/admin/ops-center/lucky-gifts/experiments/lucky_exp_route?app_code=lalu",
			body:   `{"status":"paused"}`, permission: "lucky-gift:experiment", wantStatus: http.StatusOK,
		},
		{
			name: "patch rejects view permission", method: http.MethodPatch,
			target: "/api/v1/admin/ops-center/lucky-gifts/experiments/lucky_exp_route?app_code=lalu",
			body:   `{"status":"paused"}`, permission: "lucky-gift:view", wantStatus: http.StatusForbidden,
		},
		{
			name: "end rejects view permission", method: http.MethodPost,
			target: "/api/v1/admin/ops-center/lucky-gifts/experiments/lucky_exp_route/end?app_code=lalu",
			body:   `{"winner_arm":"control"}`, permission: "lucky-gift:view", wantStatus: http.StatusForbidden,
		},
		{
			name: "overrides rejects update permission", method: http.MethodPut,
			target: "/api/v1/admin/ops-center/lucky-gifts/experiments/lucky_exp_route/overrides?app_code=lalu",
			body:   `{"upserts":[]}`, permission: "lucky-gift:update", wantStatus: http.StatusForbidden,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			engine := gin.New()
			engine.Use(func(c *gin.Context) {
				c.Set(middleware.ContextPermissions, []string{tt.permission})
				c.Set(middleware.ContextUserID, uint(7))
				c.Next()
			})
			RegisterRoutes(engine.Group("/api/v1"), New(nil, luckygiftmodule.New(experimentPermissionClient{}, 0, nil)))
			recorder := httptest.NewRecorder()
			request := httptest.NewRequest(tt.method, tt.target, strings.NewReader(tt.body))
			request.Header.Set("Content-Type", "application/json")
			engine.ServeHTTP(recorder, request)
			if recorder.Code != tt.wantStatus {
				t.Fatalf("status=%d want=%d body=%s", recorder.Code, tt.wantStatus, recorder.Body.String())
			}
		})
	}
}

func TestPoolAdjustmentRoutesRequireDirectionSpecificPermission(t *testing.T) {
	gin.SetMode(gin.TestMode)
	tests := []struct {
		name       string
		path       string
		permission string
		wantStatus int
	}{
		{name: "credit allowed", path: "credit", permission: "lucky-gift:pool-credit", wantStatus: http.StatusOK},
		{name: "credit rejects debit permission", path: "credit", permission: "lucky-gift:pool-debit", wantStatus: http.StatusForbidden},
		{name: "debit allowed", path: "debit", permission: "lucky-gift:pool-debit", wantStatus: http.StatusOK},
		{name: "debit rejects view permission", path: "debit", permission: "lucky-gift:view", wantStatus: http.StatusForbidden},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			engine := gin.New()
			engine.Use(func(c *gin.Context) {
				c.Set(middleware.ContextPermissions, []string{tt.permission})
				c.Set(middleware.ContextUserID, uint(7))
				c.Next()
			})
			RegisterRoutes(engine.Group("/api/v1"), New(nil, luckygiftmodule.New(poolPermissionClient{}, 0, nil)))
			recorder := httptest.NewRecorder()
			request := httptest.NewRequest(
				http.MethodPost,
				"/api/v1/admin/ops-center/lucky-gifts/pools/"+tt.path+"?app_code=aslan&pool_id=lucky&strategy_version=dynamic_v3",
				strings.NewReader(`{"adjustment_id":"route-adjustment","amount_coins":1,"reason":"permission test"}`),
			)
			request.Header.Set("Content-Type", "application/json")
			engine.ServeHTTP(recorder, request)
			if recorder.Code != tt.wantStatus {
				t.Fatalf("status=%d want=%d body=%s", recorder.Code, tt.wantStatus, recorder.Body.String())
			}
		})
	}
}
