package integration_test

import (
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	"strings"
	"testing"
	"time"

	"github.com/golang/protobuf/jsonpb"
	"github.com/grpc-ecosystem/grpc-gateway/runtime"
	spb "google.golang.org/genproto/googleapis/rpc/status"
	"google.golang.org/grpc/codes"
)

func runServer(ctx context.Context, t *testing.T, port uint16) {
	opt := runtime.WithProtoErrorHandler(runtime.DefaultHTTPProtoErrorHandler)
	if err := runGateway(ctx, fmt.Sprintf(":%d", port), opt); err != nil {
		t.Errorf("runGateway() failed with %v; want success", err)
	}
}

func TestWithProtoErrorHandler(t *testing.T) {
	ctx := context.Background()
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	const port = 8082
	go runServer(ctx, t, port)
	if err := waitForGateway(ctx, 8082); err != nil {
		t.Errorf("waitForGateway(ctx, 8082) failed with %v; want success", err)
	}
	testEcho(t, port, "application/json")
	testEchoBody(t, port)
}

func TestABEWithProtoErrorHandler(t *testing.T) {
	if testing.Short() {
		t.Skip()
		return
	}

	ctx := context.Background()
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	const port = 8083
	go runServer(ctx, t, port)
	if err := waitForGateway(ctx, 8083); err != nil {
		t.Errorf("waitForGateway(ctx, 8083) failed with %v; want success", err)
	}

	testABECreate(t, port)
	testABECreateBody(t, port)
	testABEBulkCreate(t, port)
	testABELookup(t, port)
	testABELookupNotFoundWithProtoError(t, port)
	testABEList(t, port)
	testABEBulkEcho(t, port)
	testABEBulkEchoZeroLength(t, port)
	testAdditionalBindings(t, port)
}

func testABELookupNotFoundWithProtoError(t *testing.T, port uint16) {
	url := fmt.Sprintf("http://localhost:%d/v1/example/a_bit_of_everything", port)
	uuid := "not_exist"
	url = fmt.Sprintf("%s/%s", url, uuid)
	resp, err := http.Get(url)
	if err != nil {
		t.Errorf("http.Get(%q) failed with %v; want success", url, err)
		return
	}
	defer resp.Body.Close()

	buf, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Errorf("ioutil.ReadAll(resp.Body) failed with %v; want success", err)
		return
	}

	if got, want := resp.StatusCode, http.StatusNotFound; got != want {
		t.Errorf("resp.StatusCode = %d; want %d", got, want)
		t.Logf("%s", buf)
		return
	}

	var msg spb.Status
	if err := jsonpb.UnmarshalString(string(buf), &msg); err != nil {
		t.Errorf("jsonpb.UnmarshalString(%s, &msg) failed with %v; want success", buf, err)
		return
	}

	if got, want := msg.Code, int32(codes.NotFound); got != want {
		t.Errorf("msg.Code = %d; want %d", got, want)
		return
	}

	if got, want := msg.Message, "not found"; got != want {
		t.Errorf("msg.Message = %s; want %s", got, want)
		return
	}

	if got, want := resp.Header.Get("Grpc-Metadata-Uuid"), uuid; got != want {
		t.Errorf("Grpc-Metadata-Uuid was %s, wanted %s", got, want)
	}
	if got, want := resp.Trailer.Get("Grpc-Trailer-Foo"), "foo2"; got != want {
		t.Errorf("Grpc-Trailer-Foo was %q, wanted %q", got, want)
	}
	if got, want := resp.Trailer.Get("Grpc-Trailer-Bar"), "bar2"; got != want {
		t.Errorf("Grpc-Trailer-Bar was %q, wanted %q", got, want)
	}
}

func TestUnknownPathWithProtoError(t *testing.T) {
	ctx := context.Background()
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	const port = 8084
	go runServer(ctx, t, port)
	if err := waitForGateway(ctx, 8084); err != nil {
		t.Errorf("waitForGateway(ctx, 8084) failed with %v; want success", err)
	}

	url := fmt.Sprintf("http://localhost:%d", port)
	resp, err := http.Post(url, "application/json", strings.NewReader("{}"))
	if err != nil {
		t.Errorf("http.Post(%q) failed with %v; want success", url, err)
		return
	}
	defer resp.Body.Close()
	buf, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Errorf("iotuil.ReadAll(resp.Body) failed with %v; want success", err)
		return
	}

	if got, want := resp.StatusCode, http.StatusNotImplemented; got != want {
		t.Errorf("resp.StatusCode = %d; want %d", got, want)
		t.Logf("%s", buf)
	}

	var msg spb.Status
	if err := jsonpb.UnmarshalString(string(buf), &msg); err != nil {
		t.Errorf("jsonpb.UnmarshalString(%s, &msg) failed with %v; want success", buf, err)
		return
	}

	if got, want := msg.Code, int32(codes.Unimplemented); got != want {
		t.Errorf("msg.Code = %d; want %d", got, want)
		return
	}

	if msg.Message == "" {
		t.Errorf("msg.Message should not be empty")
		return
	}
}

func TestMethodNotAllowedWithProtoError(t *testing.T) {
	ctx := context.Background()
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	const port = 8085
	go runServer(ctx, t, port)

	// Waiting for the server's getting available.
	// TODO(yugui) find a better way to wait
	time.Sleep(100 * time.Millisecond)

	url := fmt.Sprintf("http://localhost:%d/v1/example/echo/myid", port)
	resp, err := http.Get(url)
	if err != nil {
		t.Errorf("http.Post(%q) failed with %v; want success", url, err)
		return
	}
	defer resp.Body.Close()
	buf, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		t.Errorf("iotuil.ReadAll(resp.Body) failed with %v; want success", err)
		return
	}

	if got, want := resp.StatusCode, http.StatusNotImplemented; got != want {
		t.Errorf("resp.StatusCode = %d; want %d", got, want)
		t.Logf("%s", buf)
	}

	var msg spb.Status
	if err := jsonpb.UnmarshalString(string(buf), &msg); err != nil {
		t.Errorf("jsonpb.UnmarshalString(%s, &msg) failed with %v; want success", buf, err)
		return
	}

	if got, want := msg.Code, int32(codes.Unimplemented); got != want {
		t.Errorf("msg.Code = %d; want %d", got, want)
		return
	}

	if msg.Message == "" {
		t.Errorf("msg.Message should not be empty")
		return
	}
}
