package server

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

	"github.com/gin-gonic/gin"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/health"
	healthpb "google.golang.org/grpc/health/grpc_health_v1"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/reflection"
)

func TestDynamicGatewayInvokesUnaryRPC(t *testing.T) {
	listener, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatal(err)
	}
	grpcServer := grpc.NewServer()
	healthServer := health.NewServer()
	healthServer.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
	healthpb.RegisterHealthServer(grpcServer, healthServer)
	reflection.Register(grpcServer)
	go func() { _ = grpcServer.Serve(listener) }()
	t.Cleanup(func() {
		grpcServer.Stop()
		_ = listener.Close()
	})

	gateway, err := newDynamicGateway(listener.Addr().String(), []string{"grpc.health.v1.Health.Check"})
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = gateway.Close() })

	gin.SetMode(gin.TestMode)
	engine := gin.New()
	engine.POST("/rpc/:method", gateway.handle)
	request := httptest.NewRequest(http.MethodPost, "/rpc/grpc.health.v1.Health.Check", strings.NewReader(`{"service":""}`))
	response := httptest.NewRecorder()
	engine.ServeHTTP(response, request)

	if response.Code != http.StatusOK {
		t.Fatalf("unexpected HTTP status: %d", response.Code)
	}
	var payload struct {
		Code int32           `json:"code"`
		Data json.RawMessage `json:"data"`
	}
	if err := json.Unmarshal(response.Body.Bytes(), &payload); err != nil {
		t.Fatal(err)
	}
	if payload.Code != 0 || !strings.Contains(string(payload.Data), `"SERVING"`) {
		t.Fatalf("unexpected response: %s", response.Body.String())
	}
}

func TestDynamicGatewayDeniesMethodsByDefault(t *testing.T) {
	gateway := &dynamicGateway{allow: map[string]struct{}{}}
	gin.SetMode(gin.TestMode)
	engine := gin.New()
	engine.POST("/rpc/:method", gateway.handle)
	request := httptest.NewRequest(http.MethodPost, "/rpc/grpc.health.v1.Health.Check", strings.NewReader(`{}`))
	response := httptest.NewRecorder()
	engine.ServeHTTP(response, request)
	if response.Code != http.StatusOK {
		t.Fatalf("dynamic RPC errors must use HTTP 200, got %d", response.Code)
	}
	var payload dynamicRPCResponse
	if err := json.Unmarshal(response.Body.Bytes(), &payload); err != nil {
		t.Fatal(err)
	}
	if payload.Code != int32(codes.PermissionDenied) {
		t.Fatalf("expected permission denied, got %s", response.Body.String())
	}
}

func TestOutgoingMetadataFiltersHeaders(t *testing.T) {
	request := httptest.NewRequest(http.MethodPost, "/", nil)
	request.Header.Set("Authorization", "Bearer token")
	request.Header.Set("X-Request-ID", "request-id")
	request.Header.Set("Content-Type", "application/json")
	ctx := outgoingMetadata(request)
	forwarded, ok := metadata.FromOutgoingContext(ctx)
	if !ok || len(forwarded.Get("authorization")) != 1 || len(forwarded.Get("x-request-id")) != 1 {
		t.Fatalf("expected forwarded metadata: %v", forwarded)
	}
	if len(forwarded.Get("content-type")) != 0 {
		t.Fatalf("content-type must not be forwarded: %v", forwarded)
	}
}
