package http

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	gamev1 "hyapp.local/api/proto/game/v1"
	"hyapp/services/gateway-service/internal/auth"
	"hyapp/services/gateway-service/internal/transport/http/httpkit"
)

func TestListGamesUsesAuthenticatedUserRegionAndReturnsEnvelope(t *testing.T) {
	gameClient := &fakeGatewayGameClient{listResp: &gamev1.ListGamesResponse{
		Games: []*gamev1.AppGame{{
			GameId:       "demo_rocket_001",
			PlatformCode: "demo",
			NameKey:      "game.demo_rocket_001.name",
			Name:         "Rocket",
			LaunchMode:   "h5_popup",
			Orientation:  "portrait",
			Enabled:      true,
		}},
		ServerTimeMs: 1700000000000,
	}}
	handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{regionID: 1001})
	handler.SetGameClient(gameClient)
	router := handler.Routes(auth.NewVerifier("secret"))
	request := httptest.NewRequest(http.MethodGet, "/api/v1/games?scene=voice_room&room_id=room-1", nil)
	request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
	request.Header.Set("X-App-Language", "en")
	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())
	}
	if gameClient.lastList.GetUserId() != 42 || gameClient.lastList.GetRegionId() != 1001 || gameClient.lastList.GetScene() != "voice_room" || gameClient.lastList.GetRoomId() != "room-1" {
		t.Fatalf("ListGames request mismatch: %+v", gameClient.lastList)
	}
	var envelope httpkit.ResponseEnvelope
	if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil {
		t.Fatalf("decode response failed: %v", err)
	}
	data := envelope.Data.(map[string]any)
	games := data["games"].([]any)
	first := games[0].(map[string]any)
	if first["platform_code"] != "demo" || first["game_id"] != "demo_rocket_001" {
		t.Fatalf("game response mismatch: %+v", first)
	}
}

func TestListRecentGamesUsesAuthenticatedUserRegionAndLimit(t *testing.T) {
	gameClient := &fakeGatewayGameClient{recentResp: &gamev1.ListGamesResponse{
		Games: []*gamev1.AppGame{{
			GameId:       "viva_2",
			PlatformCode: "vivagames",
			Name:         "Rich Forever",
			LaunchMode:   "h5_popup",
			Orientation:  "portrait",
			Enabled:      true,
		}},
		ServerTimeMs: 1700000000000,
	}}
	handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{regionID: 1001})
	handler.SetGameClient(gameClient)
	router := handler.Routes(auth.NewVerifier("secret"))
	request := httptest.NewRequest(http.MethodGet, "/api/v1/games/recent?scene=voice_room&room_id=room-1&limit=4", nil)
	request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
	request.Header.Set("X-App-Language", "en")
	request.Header.Set("X-App-Platform", "android")
	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())
	}
	if gameClient.lastRecent.GetUserId() != 42 || gameClient.lastRecent.GetRegionId() != 1001 || gameClient.lastRecent.GetScene() != "voice_room" || gameClient.lastRecent.GetRoomId() != "room-1" || gameClient.lastRecent.GetPageSize() != 4 || gameClient.lastRecent.GetClientPlatform() != "android" {
		t.Fatalf("ListRecentGames request mismatch: %+v", gameClient.lastRecent)
	}
	var envelope httpkit.ResponseEnvelope
	if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil {
		t.Fatalf("decode response failed: %v", err)
	}
	data := envelope.Data.(map[string]any)
	games := data["games"].([]any)
	first := games[0].(map[string]any)
	if first["platform_code"] != "vivagames" || first["game_id"] != "viva_2" {
		t.Fatalf("recent game response mismatch: %+v", first)
	}
}

func TestGetBridgeScriptReturnsUniversalScriptConfig(t *testing.T) {
	gameClient := &fakeGatewayGameClient{bridgeResp: &gamev1.GetBridgeScriptResponse{
		Config: &gamev1.GameBridgeScriptConfig{
			BridgeScriptUrl:     "https://cdn.example.test/game-bridge.js",
			BridgeScriptVersion: "20260608.1",
			BridgeScriptSha256:  "abc",
		},
		ServerTimeMs: 1700000000000,
	}}
	handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{regionID: 1001})
	handler.SetGameClient(gameClient)
	router := handler.Routes(auth.NewVerifier("secret"))
	request := httptest.NewRequest(http.MethodGet, "/api/v1/games/bridge-script", nil)
	request.Header.Set("Authorization", "Bearer "+signGatewayToken(t, "secret", 42))
	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())
	}
	if gameClient.lastBridge.GetMeta().GetActorUserId() != 42 {
		t.Fatalf("GetBridgeScript request mismatch: %+v", gameClient.lastBridge)
	}
	var envelope httpkit.ResponseEnvelope
	if err := json.NewDecoder(recorder.Body).Decode(&envelope); err != nil {
		t.Fatalf("decode response failed: %v", err)
	}
	data := envelope.Data.(map[string]any)
	if data["bridge_script_url"] != "https://cdn.example.test/game-bridge.js" || data["bridgeScriptUrl"] != "https://cdn.example.test/game-bridge.js" || data["bridge_script_version"] != "20260608.1" || data["bridge_script_sha256"] != "abc" {
		t.Fatalf("bridge script response mismatch: %+v", data)
	}
}

func TestLaunchGameForwardsDisplayUserID(t *testing.T) {
	gameClient := &fakeGatewayGameClient{launchResp: &gamev1.LaunchGameResponse{
		SessionId:   "game_sess_1",
		LaunchUrl:   "https://provider.example/h5?session_token=token",
		ExpiresAtMs: 1700000900000,
		Orientation: "portrait",
	}}
	handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{})
	handler.SetGameClient(gameClient)
	router := handler.Routes(auth.NewVerifier("secret"))
	accessToken := signGatewayToken(t, "secret", 42)
	request := httptest.NewRequest(http.MethodPost, "/api/v1/games/demo_rocket_001/launch", bytes.NewReader([]byte(`{"scene":"voice_room","room_id":"room-1"}`)))
	request.Header.Set("Authorization", "Bearer "+accessToken)
	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())
	}
	if gameClient.lastLaunch.GetGameId() != "demo_rocket_001" || gameClient.lastLaunch.GetDisplayUserId() != "100001" || gameClient.lastLaunch.GetRoomId() != "room-1" || gameClient.lastLaunch.GetAccessToken() != accessToken {
		t.Fatalf("LaunchGame request mismatch: %+v", gameClient.lastLaunch)
	}
}

func TestGameCallbackIsRawPublicResponse(t *testing.T) {
	gameClient := &fakeGatewayGameClient{callbackResp: &gamev1.CallbackResponse{RawBody: []byte(`{"code":0}`), ContentType: "application/json"}}
	handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{})
	handler.SetGameClient(gameClient)
	router := handler.Routes(auth.NewVerifier("secret"))
	request := httptest.NewRequest(http.MethodPost, "/api/v1/game-callbacks/demo/change_coin?nonce=1", bytes.NewReader([]byte(`{"provider_order_id":"p1"}`)))
	request.Header.Set("X-App-Code", "lalu")
	recorder := httptest.NewRecorder()

	router.ServeHTTP(recorder, request)

	if recorder.Code != http.StatusOK || recorder.Body.String() != `{"code":0}` {
		t.Fatalf("callback response must not use app envelope: status=%d body=%s", recorder.Code, recorder.Body.String())
	}
	if gameClient.lastCallback.GetPlatformCode() != "demo" || gameClient.lastCallback.GetOperation() != "change_coin" || string(gameClient.lastCallback.GetRawBody()) != `{"provider_order_id":"p1"}` || gameClient.lastCallback.GetQuery()["nonce"] != "1" {
		t.Fatalf("callback request mismatch: %+v", gameClient.lastCallback)
	}
}

type fakeGatewayGameClient struct {
	listResp     *gamev1.ListGamesResponse
	recentResp   *gamev1.ListGamesResponse
	bridgeResp   *gamev1.GetBridgeScriptResponse
	launchResp   *gamev1.LaunchGameResponse
	callbackResp *gamev1.CallbackResponse
	lastList     *gamev1.ListGamesRequest
	lastRecent   *gamev1.ListRecentGamesRequest
	lastBridge   *gamev1.GetBridgeScriptRequest
	lastLaunch   *gamev1.LaunchGameRequest
	lastCallback *gamev1.CallbackRequest
}

func (f *fakeGatewayGameClient) ListGames(_ context.Context, req *gamev1.ListGamesRequest) (*gamev1.ListGamesResponse, error) {
	f.lastList = req
	if f.listResp != nil {
		return f.listResp, nil
	}
	return &gamev1.ListGamesResponse{}, nil
}

func (f *fakeGatewayGameClient) ListRecentGames(_ context.Context, req *gamev1.ListRecentGamesRequest) (*gamev1.ListGamesResponse, error) {
	f.lastRecent = req
	if f.recentResp != nil {
		return f.recentResp, nil
	}
	return &gamev1.ListGamesResponse{}, nil
}

func (f *fakeGatewayGameClient) GetBridgeScript(_ context.Context, req *gamev1.GetBridgeScriptRequest) (*gamev1.GetBridgeScriptResponse, error) {
	f.lastBridge = req
	if f.bridgeResp != nil {
		return f.bridgeResp, nil
	}
	return &gamev1.GetBridgeScriptResponse{}, nil
}

func (f *fakeGatewayGameClient) LaunchGame(_ context.Context, req *gamev1.LaunchGameRequest) (*gamev1.LaunchGameResponse, error) {
	f.lastLaunch = req
	if f.launchResp != nil {
		return f.launchResp, nil
	}
	return &gamev1.LaunchGameResponse{}, nil
}

func (f *fakeGatewayGameClient) HandleCallback(_ context.Context, req *gamev1.CallbackRequest) (*gamev1.CallbackResponse, error) {
	f.lastCallback = req
	if f.callbackResp != nil {
		return f.callbackResp, nil
	}
	return &gamev1.CallbackResponse{RawBody: []byte(`{"code":0}`), ContentType: "application/json"}, nil
}
