package http

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

	userv1 "hyapp.local/api/proto/user/v1"
	"hyapp/services/gateway-service/internal/appconfig"
	"hyapp/services/gateway-service/internal/auth"
	"hyapp/services/gateway-service/internal/transport/http/httpkit"
)

type countingRoomDiscoveryReader struct {
	appconfig.StaticReader
	calls int
}

func (r *countingRoomDiscoveryReader) RoomDiscoveryAllRegions(ctx context.Context, appCode string) (bool, error) {
	r.calls++
	return r.StaticReader.RoomDiscoveryAllRegions(ctx, appCode)
}

func TestListRoomFiltersReturnsAllAppScopedFiltersWhenConfigured(t *testing.T) {
	regionClient := &fakeUserRegionClient{listResp: &userv1.ListRegionsResponse{Regions: []*userv1.Region{
		{RegionId: 1001, RegionCode: "MIDDLE_EAST", Name: "Middle East", Status: "active", Countries: []string{"AE", "SA"}, SortOrder: 10},
		{RegionId: 1002, RegionCode: "SOUTH_ASIA", Name: "South Asia", Status: "active", Countries: []string{"PK"}, SortOrder: 20},
		// 即使上游违反 status 过滤契约返回禁用区域，gateway 也不能把不可用筛选值下发给 Flutter。
		{RegionId: 1003, RegionCode: "INDIA", Name: "India", Status: "disabled", Countries: []string{"IN"}, SortOrder: 30},
	}}}
	countryClient := &fakeUserCountryQueryClient{resp: &userv1.ListRegistrationCountriesResponse{Countries: []*userv1.Country{
		{CountryId: 971, CountryCode: "ae", CountryName: "United Arab Emirates", CountryDisplayName: "UAE", Flag: "🇦🇪", SortOrder: 10},
		{CountryId: 966, CountryCode: "SA", CountryName: "Saudi Arabia", CountryDisplayName: "Saudi Arabia", SortOrder: 20},
		{CountryId: 92, CountryCode: "PK", CountryName: "Pakistan", CountryDisplayName: "Pakistan", Flag: "🇵🇰", SortOrder: 30},
		// Enabled 但未配置 active 业务区域的国家不能成为可回传筛选项。
		{CountryId: 95, CountryCode: "MM", CountryName: "Myanmar", CountryDisplayName: "Myanmar", Flag: "🇲🇲", SortOrder: 40},
		{CountryId: 91, CountryCode: "IN", CountryName: "India", CountryDisplayName: "India", Flag: "🇮🇳", SortOrder: 50},
	}}}
	profileClient := &fakeUserProfileClient{}
	handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient)
	handler.SetUserRegionClient(regionClient)
	handler.SetUserCountryQueryClient(countryClient)
	configReader := &countingRoomDiscoveryReader{StaticReader: appconfig.StaticReader{RoomDiscoveryAll: map[string]bool{"wide-app": true}}}
	handler.SetAppConfigReader(configReader)
	router := handler.Routes(auth.NewVerifier("secret"))
	request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms/filters", nil)
	request.Header.Set("X-App-Code", "wide-app")
	request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "wide-app"))
	request.Header.Set("X-Request-ID", "req-room-filters")
	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 regionClient.lastList == nil || regionClient.lastList.GetStatus() != "active" || regionClient.lastList.GetMeta().GetAppCode() != "wide-app" {
		t.Fatalf("region query must be active and app-scoped: %+v", regionClient.lastList)
	}
	if countryClient.last == nil || countryClient.last.GetMeta().GetAppCode() != "wide-app" {
		t.Fatalf("country query must use the same app scope: %+v", countryClient.last)
	}
	if profileClient.lastGet != nil || regionClient.last != nil {
		t.Fatalf("all-region catalog must not narrow to the viewer: profile=%+v region=%+v", profileClient.lastGet, regionClient.last)
	}
	if configReader.calls != 1 {
		t.Fatalf("one room-filter request must resolve discovery scope once, got %d calls", configReader.calls)
	}

	var response httpkit.ResponseEnvelope
	if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
		t.Fatalf("decode room filter response failed: %v", err)
	}
	if response.Code != httpkit.CodeOK {
		t.Fatalf("unexpected envelope: %+v", response)
	}
	data := response.Data.(map[string]any)
	all := data["all"].(map[string]any)
	if all["filter_type"] != "all" || all["query_key"] != "" || all["query_value"] != "" {
		t.Fatalf("all filter mismatch: %+v", all)
	}
	regions := data["regions"].([]any)
	if len(regions) != 2 || regions[0].(map[string]any)["region_id"] != float64(1001) || regions[0].(map[string]any)["query_key"] != "region_id" || regions[0].(map[string]any)["query_value"] != "1001" || regions[1].(map[string]any)["region_code"] != "SOUTH_ASIA" {
		t.Fatalf("region filters mismatch: %+v", regions)
	}
	countries := data["countries"].([]any)
	if len(countries) != 3 {
		t.Fatalf("country filters mismatch: %+v", countries)
	}
	ae := countries[0].(map[string]any)
	if ae["filter_type"] != "country" || ae["query_key"] != "country_code" || ae["query_value"] != "AE" || ae["country_code"] != "AE" || ae["parent_region_id"] != float64(1001) || ae["country_flag"] != "🇦🇪" {
		t.Fatalf("mapped country filter mismatch: %+v", ae)
	}
	sa := countries[1].(map[string]any)
	if sa["country_flag"] != "🇸🇦" {
		t.Fatalf("empty upstream flag must use ISO flag fallback: %+v", sa)
	}
	for _, item := range countries {
		if code := item.(map[string]any)["country_code"]; code == "MM" || code == "IN" {
			t.Fatalf("country without an active region mapping must not become a filter: %+v", countries)
		}
	}
}

func TestListRoomFiltersKeepsViewerScopeInsideCurrentUserRegion(t *testing.T) {
	profileClient := &fakeUserProfileClient{regionID: 1002}
	regionClient := &fakeUserRegionClient{resp: &userv1.RegionResponse{Region: &userv1.Region{
		RegionId: 1002, RegionCode: "SOUTH_ASIA", Name: "South Asia", Status: "active", Countries: []string{"PK"}, SortOrder: 20,
	}}}
	countryClient := &fakeUserCountryQueryClient{resp: &userv1.ListRegistrationCountriesResponse{Countries: []*userv1.Country{
		{CountryId: 971, CountryCode: "AE", CountryName: "United Arab Emirates"},
		{CountryId: 92, CountryCode: "PK", CountryName: "Pakistan", Flag: "🇵🇰"},
	}}}
	handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, profileClient)
	handler.SetUserRegionClient(regionClient)
	handler.SetUserCountryQueryClient(countryClient)
	router := handler.Routes(auth.NewVerifier("secret"))
	request := httptest.NewRequest(http.MethodGet, "/api/v1/rooms/filters", nil)
	request.Header.Set("X-App-Code", "regional-app")
	request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "regional-app"))
	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 regionClient.lastList != nil || profileClient.lastGet == nil || profileClient.lastGet.GetUserId() != 42 || regionClient.last == nil || regionClient.last.GetRegionId() != 1002 {
		t.Fatalf("viewer-scope catalog must resolve only the viewer region: list=%+v user=%+v region=%+v", regionClient.lastList, profileClient.lastGet, regionClient.last)
	}
	var response httpkit.ResponseEnvelope
	if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
		t.Fatalf("decode room filter response failed: %v", err)
	}
	data := response.Data.(map[string]any)
	regions := data["regions"].([]any)
	countries := data["countries"].([]any)
	if len(regions) != 1 || regions[0].(map[string]any)["region_id"] != float64(1002) || len(countries) != 1 || countries[0].(map[string]any)["country_code"] != "PK" {
		t.Fatalf("non-Huwaa filter scope leaked another region or country: regions=%+v countries=%+v", regions, countries)
	}
}

func TestListRoomFiltersRejectsNonGETWithoutCallingUserService(t *testing.T) {
	regionClient := &fakeUserRegionClient{}
	countryClient := &fakeUserCountryQueryClient{}
	handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{})
	handler.SetUserRegionClient(regionClient)
	handler.SetUserCountryQueryClient(countryClient)
	router := handler.Routes(auth.NewVerifier("secret"))
	request := httptest.NewRequest(http.MethodPost, "/api/v1/rooms/filters", nil)
	request.Header.Set("X-App-Code", "wide-app")
	request.Header.Set("Authorization", "Bearer "+signGatewayTokenWithAppCode(t, "secret", 42, "wide-app"))
	recorder := httptest.NewRecorder()

	router.ServeHTTP(recorder, request)

	if recorder.Code != http.StatusMethodNotAllowed {
		t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
	}
	if regionClient.lastList != nil || countryClient.last != nil {
		t.Fatalf("invalid method must not call user-service: region=%+v country=%+v", regionClient.lastList, countryClient.last)
	}
}
