// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package bytestream

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"log"
	"net"
	"testing"

	"golang.org/x/net/context"
	"google.golang.org/api/transport/bytestream/internal"
	"google.golang.org/grpc"
	"google.golang.org/grpc/metadata"

	pb "google.golang.org/genproto/googleapis/bytestream"
)

const testData = "0123456789"

// A grpcServer is an in-process gRPC server, listening on a system-chosen port on
// the local loopback interface. Servers are for testing only and are not
// intended to be used in production code.
// (Copied from "cloud.google.com/internal/testutil/server_test.go")
//
// To create a server, make a new grpcServer, register your handlers, then call
// Start:
//
//	srv, err := NewServer()
//	...
//	mypb.RegisterMyServiceServer(srv.Gsrv, &myHandler)
//	....
//	srv.Start()
//
// Clients should connect to the server with no security:
//
//	conn, err := grpc.Dial(srv.Addr, grpc.WithInsecure())
//	...
type grpcServer struct {
	Addr string
	l    net.Listener
	Gsrv *grpc.Server
}

type TestSetup struct {
	ctx     context.Context
	rpcTest *grpcServer
	server  *internal.Server
	client  *Client
}

func TestClientRead(t *testing.T) {
	testCases := []struct {
		name         string
		input        string
		resourceName string
		extraBufSize int
		extraError   error
		want         string
		wantErr      bool
		wantEOF      bool
	}{
		{
			name:         "test foo",
			input:        testData,
			resourceName: "foo",
			want:         testData,
		}, {
			name:         "test bar",
			input:        testData,
			resourceName: "bar",
			want:         testData,
		}, {
			name:         "test bar extraBufSize=1",
			input:        testData,
			resourceName: "bar",
			extraBufSize: 1,
			want:         testData,
			wantEOF:      true,
		}, {
			name:         "test bar extraBufSize=2",
			input:        testData,
			resourceName: "bar",
			extraBufSize: 2,
			want:         testData,
			wantEOF:      true,
		}, {
			name:         "empty resource name",
			input:        testData,
			resourceName: "",
			extraBufSize: 1,
			wantErr:      true,
		}, {
			name:         "read after error returns error again",
			input:        testData,
			resourceName: "does not matter",
			extraBufSize: 1,
			extraError:   errors.New("some error"),
			wantErr:      true,
		},
	}

	for _, tc := range testCases {
		bufSize := len(tc.want) + tc.extraBufSize
		if bufSize == 0 {
			t.Errorf("%s: This is probably wrong. Read returning 0 bytes?", tc.name)
			continue
		}

		setup := newTestSetup(tc.input)
		r, err := setup.client.NewReader(setup.ctx, tc.resourceName)
		if err != nil {
			t.Errorf("%s: NewReader(%q): %v", tc.name, tc.resourceName, err)
			continue
		}
		if tc.extraError != nil {
			r.err = tc.extraError
		}
		buf := make([]byte, bufSize)
		gotEOF := false
		total := 0
		for total < bufSize && err == nil {
			var n int
			n, err = r.Read(buf[total:])
			total += n
		}
		if err == io.EOF {
			gotEOF = true
			err = nil
			doubleCheckBuf := make([]byte, bufSize)
			n2, err2 := r.Read(doubleCheckBuf)
			if err2 != io.EOF {
				t.Errorf("%s: read and got EOF, double-check: read %d bytes got err=%v", tc.name, n2, err2)
				continue
			}
		}
		setup.Close()

		if gotErr := err != nil; tc.wantErr != gotErr {
			t.Errorf("%s: read %d bytes, got err=%v, wantErr=%t", tc.name, total, err, tc.wantErr)
			continue
		}
		if tc.wantEOF != gotEOF {
			t.Errorf("%s: read %d bytes, gotEOF=%t, wantEOF=%t", tc.name, total, gotEOF, tc.wantEOF)
			continue
		}
		if got := string(buf[:total]); got != tc.want {
			t.Errorf("%s: read %q, want %q", tc.name, got, tc.want)
			continue
		}
	}
}

func TestClientWrite(t *testing.T) {
	testCases := []struct {
		name         string
		resourceName string
		maxBufSize   int
		data         string
		results      []int
		wantWriteErr bool
		wantCloseErr bool
	}{
		{
			name:         "test foo",
			resourceName: "foo",
			data:         testData,
			results:      []int{len(testData)},
		}, {
			name:         "empty resource name",
			resourceName: "",
			data:         testData,
			results:      []int{10},
			//wantWriteErr: true,
			wantCloseErr: true,
		}, {
			name:         "test bar",
			resourceName: "bar",
			data:         testData,
			results:      []int{len(testData)},
		},
	}

	var setup *TestSetup

tcFor:
	for _, tc := range testCases {
		if setup != nil {
			setup.Close()
		}
		setup = newTestSetup("")
		buf := []byte(tc.data)
		var ofs int
		w, err := setup.client.NewWriter(setup.ctx, tc.resourceName)
		if err != nil {
			t.Errorf("%s: NewWriter(): %v", tc.name, err)
			continue
		}

		for i := 0; i < len(tc.results); i++ {
			if ofs >= len(tc.data) {
				t.Errorf("%s [%d]: Attempting to write more than tc.input: ofs=%d len(buf)=%d",
					tc.name, i, ofs, len(tc.data))
				continue tcFor
			}
			n, err := w.Write(buf[ofs:])
			ofs += n
			if gotErr := err != nil; gotErr != tc.wantWriteErr {
				t.Errorf("%s [%d]: Write() got n=%d err=%v, wantWriteErr=%t", tc.name, i, n, err, tc.wantWriteErr)
				continue tcFor
			} else if tc.wantWriteErr && i+1 < len(tc.results) {
				t.Errorf("%s: wantWriteErr and got err after %d results, len(results)=%d is too long.", tc.name, i+1, len(tc.results))
				continue tcFor
			}
			if n != tc.results[i] {
				t.Errorf("%s [%d]: Write() wrote %d bytes, want %d bytes", tc.name, i, n, tc.results[i])
				continue tcFor
			}
		}

		err = w.Close()
		if gotErr := err != nil; gotErr != tc.wantCloseErr {
			t.Errorf("%s: Close() got err=%v, wantCloseErr=%t", tc.name, err, tc.wantCloseErr)
			continue tcFor
		}
	}
	setup.Close()
}

func TestClientRead_AfterSetupClose(t *testing.T) {
	setup := newTestSetup("closed")
	setup.Close()
	_, err := setup.client.NewReader(setup.ctx, "should fail")
	if err == nil {
		t.Errorf("NewReader(%q): err=%v", "should fail", err)
	}
}

func TestClientWrite_AfterSetupClose(t *testing.T) {
	setup := newTestSetup("closed")
	setup.Close()
	_, err := setup.client.NewWriter(setup.ctx, "should fail")
	if err == nil {
		t.Fatalf("NewWriter(%q): err=%v", "shoudl fail", err)
	}
}

type UnsendableWriteClient struct {
	closeAndRecvWriteResponse *pb.WriteResponse
	closeAndRecvError         error
}

func (w *UnsendableWriteClient) Send(*pb.WriteRequest) error {
	if w.closeAndRecvError != nil {
		return nil
	}
	return errors.New("UnsendableWriteClient.Send() fails unless closeAndRecvError is set")
}

func (w *UnsendableWriteClient) CloseAndRecv() (*pb.WriteResponse, error) {
	if w.closeAndRecvError == nil {
		log.Fatalf("UnsendableWriteClient.Close() when closeAndRecvError == nil.")
	}
	return w.closeAndRecvWriteResponse, w.closeAndRecvError
}

func (w *UnsendableWriteClient) Context() context.Context {
	log.Fatalf("UnsendableWriteClient.Context() should never be called")
	return context.Background()
}
func (w *UnsendableWriteClient) CloseSend() error {
	return errors.New("UnsendableWriteClient.CloseSend() should never be called")
}
func (w *UnsendableWriteClient) Header() (metadata.MD, error) {
	log.Fatalf("UnsendableWriteClient.Header() should never be called")
	return metadata.MD{}, nil
}
func (w *UnsendableWriteClient) Trailer() metadata.MD {
	log.Fatalf("UnsendableWriteClient.Trailer() should never be called")
	return metadata.MD{}
}
func (fake *UnsendableWriteClient) SendMsg(m interface{}) error {
	log.Fatalf("UnsendableWriteClient.SendMsg() should never be called")
	return nil
}
func (fake *UnsendableWriteClient) RecvMsg(m interface{}) error {
	log.Fatalf("UnsendableWriteClient.RecvMsg() should never be called")
	return nil
}

func TestClientWrite_WriteFails(t *testing.T) {
	setup := newTestSetup("")
	w, err := setup.client.NewWriter(setup.ctx, "")
	if err != nil {
		t.Fatalf("NewWriter(): %v", err)
	}
	defer setup.Close()
	w.writeClient = &UnsendableWriteClient{}
	_, err = w.Write([]byte(testData))
	if err == nil {
		t.Errorf("Write() should fail")
	}
}

func TestClientWrite_CloseAndRecvFails(t *testing.T) {
	setup := newTestSetup("")
	w, err := setup.client.NewWriter(setup.ctx, "CloseAndRecvFails")
	if err != nil {
		t.Fatalf("NewWriter(): %v", err)
	}
	defer setup.Close()
	n, err := w.Write([]byte(testData))
	if err != nil {
		t.Errorf("Write() failed: %v", err)
		return
	}
	if n != len(testData) {
		t.Errorf("Write() got n=%d, want n=%d", n, len(testData))
		return
	}
	w.writeClient = &UnsendableWriteClient{
		closeAndRecvError: errors.New("CloseAndRecv() must fail"),
	}
	if err = w.Close(); err == nil {
		t.Errorf("Close() should fail")
		return
	}
}

type TestWriteHandler struct {
	buf  bytes.Buffer // bytes.Buffer implements io.Writer
	name string       // This service can handle one name only.
}

func (w *TestWriteHandler) GetWriter(ctx context.Context, name string, initOffset int64) (io.Writer, error) {
	if w.name == "" {
		w.name = name
	} else if w.name != name {
		return nil, fmt.Errorf("writer already has name=%q, now a new name=%q confuses me", w.name, name)
	}
	// initOffset is ignored.
	return &w.buf, nil
}

func (w *TestWriteHandler) Close(ctx context.Context, name string) error {
	w.name = ""
	w.buf.Reset()
	return nil
}

type TestReadHandler struct {
	buf  string
	name string // This service can handle one name only.
}

// GetWriter() returns an io.ReaderAt to accept reads from the given name.
func (r *TestReadHandler) GetReader(ctx context.Context, name string) (io.ReaderAt, error) {
	if r.name == "" {
		r.name = name
	} else if r.name != name {
		return nil, fmt.Errorf("reader already has name=%q, now a new name=%q confuses me", r.name, name)
	}
	return bytes.NewReader([]byte(r.buf)), nil
}

// Close does nothing.
func (r *TestReadHandler) Close(ctx context.Context, name string) error {
	return nil
}

// newGRPCServer creates a new grpcServer. The grpcServer will be listening for gRPC connections
// at the address named by the Addr field, without TLS.
func newGRPCServer() (*grpcServer, error) {
	l, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		return nil, err
	}
	s := &grpcServer{
		Addr: l.Addr().String(),
		l:    l,
		Gsrv: grpc.NewServer(),
	}
	return s, nil
}

// Start causes the server to start accepting incoming connections.
// Call Start after registering handlers.
func (s *grpcServer) Start() {
	go s.Gsrv.Serve(s.l)
}

// Close shuts down the server.
func (s *grpcServer) Close() {
	s.Gsrv.Stop()
	s.l.Close()
}

func newTestSetup(input string) *TestSetup {
	testSetup := &TestSetup{
		ctx: context.Background(),
	}
	testReadHandler := &TestReadHandler{
		buf: input,
	}
	var err error
	if testSetup.rpcTest, err = newGRPCServer(); err != nil {
		log.Fatalf("newGRPCServer: %v", err)
	}
	if testSetup.server, err = internal.NewServer(testSetup.rpcTest.Gsrv, testReadHandler, &TestWriteHandler{}); err != nil {
		log.Fatalf("internal.NewServer: %v", err)
	}
	testSetup.rpcTest.Start()

	conn, err := grpc.Dial(testSetup.rpcTest.Addr, grpc.WithInsecure())
	if err != nil {
		log.Fatalf("grpc.Dial: %v", err)
	}
	testSetup.client = NewClient(conn, grpc.FailFast(true))
	return testSetup
}

func (testSetup *TestSetup) Close() {
	testSetup.rpcTest.Close()
}
