/*
 * Copyright 2021 CloudWeGo Authors
 *
 * 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 netpoll

import (
	"bytes"
	"context"
	"net/http"
	"strings"
	"testing"
	"time"

	"github.com/cloudwego/netpoll"

	"github.com/cloudwego/kitex/internal/mocks"
	remotemocks "github.com/cloudwego/kitex/internal/mocks/remote"
	"github.com/cloudwego/kitex/internal/test"
	"github.com/cloudwego/kitex/pkg/remote"
	"github.com/cloudwego/kitex/pkg/remote/trans"
	"github.com/cloudwego/kitex/pkg/rpcinfo"
)

var (
	cliTransHdlr     remote.ClientTransHandler
	httpCilTransHdlr remote.ClientTransHandler
	cilopt           *remote.ClientOption
)

func init() {
	cilopt = &remote.ClientOption{
		SvcInfo: mocks.ServiceInfo(),
		Codec: &MockCodec{
			EncodeFunc: nil,
			DecodeFunc: nil,
		},
		Dialer: NewDialer(),
	}

	cliTransHdlr, _ = NewCliTransHandlerFactory().NewTransHandler(cilopt)
	httpCilTransHdlr, _ = NewHTTPCliTransHandlerFactory().NewTransHandler(cilopt)
}

// TestHTTPWrite test http_client_handler Write return err
func TestHTTPWrite(t *testing.T) {
	// 1. prepare mock data
	conn := &MockNetpollConn{
		WriterFunc: func() netpoll.Writer {
			return netpoll.NewWriter(&bytes.Buffer{})
		},
	}
	rwTimeout := time.Second
	cfg := rpcinfo.NewRPCConfig()
	rpcinfo.AsMutableRPCConfig(cfg).SetReadWriteTimeout(rwTimeout)
	to := rpcinfo.NewEndpointInfo("", "", nil, map[string]string{
		rpcinfo.HTTPURL: "https://example.com",
	})
	ri := rpcinfo.NewRPCInfo(nil, to, nil, cfg, rpcinfo.NewRPCStats())
	ctx := context.Background()
	msg := remote.NewMessage(nil, ri, remote.Reply, remote.Client)

	// 2. test
	ctx, err := httpCilTransHdlr.Write(ctx, conn, msg)
	// check ctx/err not nil
	test.Assert(t, err == nil, err)
	test.Assert(t, ctx != nil)
}

// TestHTTPRead test http_client_handler Read return err
func TestHTTPRead(t *testing.T) {
	// 1. prepare mock data
	rwTimeout := time.Second

	var readTimeout time.Duration
	var isReaderBufReleased bool
	conn := &MockNetpollConn{
		SetReadTimeoutFunc: func(timeout time.Duration) (e error) {
			readTimeout = timeout
			return nil
		},
		ReaderFunc: func() (r netpoll.Reader) {
			reader := &MockNetpollReader{
				ReleaseFunc: func() (err error) {
					isReaderBufReleased = true
					return nil
				},
			}
			return reader
		},
	}

	cfg := rpcinfo.NewRPCConfig()
	rpcinfo.AsMutableRPCConfig(cfg).SetRPCTimeout(rwTimeout)
	ri := rpcinfo.NewRPCInfo(nil, nil, nil, cfg, rpcinfo.NewRPCStats())
	ctx := context.Background()
	msg := remote.NewMessage(nil, ri, remote.Reply, remote.Client)

	// 2. test
	ctx, err := httpCilTransHdlr.Read(ctx, conn, msg)
	// check ctx/err not nil
	test.Assert(t, ctx != nil)
	test.Assert(t, err != nil)

	rpcinfo.AsMutableRPCConfig(cfg).SetRPCTimeout(rwTimeout)
	httpCilTransHdlr.OnError(ctx, err, conn)
	test.Assert(t, readTimeout == trans.GetReadTimeout(ri.Config()))
	test.Assert(t, isReaderBufReleased)
}

// TestHTTPPanicAfterRead test http_client_handler OnMessage success
func TestHTTPOnMessage(t *testing.T) {
	// 1. prepare mock data
	svcInfo := mocks.ServiceInfo()
	svcSearcher := remotemocks.NewDefaultSvcSearcher()
	ri := rpcinfo.NewRPCInfo(nil, nil, rpcinfo.NewInvocation(svcInfo.ServiceName, method), nil, rpcinfo.NewRPCStats())
	ctx := rpcinfo.NewCtxWithRPCInfo(context.Background(), ri)
	remote.WithServiceSearcher(ctx, svcSearcher)
	recvMsg := remote.NewMessage(nil, ri, remote.Call, remote.Server)
	sendMsg := remote.NewMessage(svcInfo.MethodInfo(context.Background(), method).NewResult(), ri, remote.Reply, remote.Server)

	// 2. test
	_, err := httpCilTransHdlr.OnMessage(ctx, recvMsg, sendMsg)
	test.Assert(t, err == nil, err)
}

// TestAddMetaInfo test http_client_handler addMetaInfo success
func TestAddMetaInfo(t *testing.T) {
	// 1. prepare mock data
	cfg := rpcinfo.NewRPCConfig()
	ri := rpcinfo.NewRPCInfo(nil, nil, nil, cfg, rpcinfo.NewRPCStats())
	var req interface{}
	msg := remote.NewMessage(req, ri, remote.Reply, remote.Client)
	h := http.Header{}

	// 2. test
	head, wantHead := "Head", "HTTP/1.1 200 OK"
	msg.Tags()[rpcinfo.HTTPHeader] = http.Header{
		head: []string{wantHead},
	}
	err := addMetaInfo(msg, h)
	getHead := h.Get(head)
	test.Assert(t, err == nil)
	test.Assert(t, getHead == wantHead)
}

// TestReadLine test http_client_handler readLine success
func TestReadLine(t *testing.T) {
	// 1. prepare mock data
	wantHead := "HTTP/1.1 200 OK"
	body := "{\"code\":0,\"data\":[\"mobile\",\"xxxxxxx\"],\"msg\":\"ok\"}"
	resp := []byte(wantHead + "\r\nDate: Thu, 16 Aug 2018 03:10:03 GMT\r\nKeep-Alive: timeout=5, max=100\r\nConnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n" + body)
	reader := remote.NewReaderBuffer(resp)

	// 2. test
	getHead, _ := readLine(reader)
	test.Assert(t, strings.Compare(string(getHead), wantHead) == 0)
}

// TestSkipToBody test http_client_handler skipToBody success
func TestSkipToBody(t *testing.T) {
	// 1. prepare mock data
	head := "HTTP/1.1 200 OK"
	wantBody := "{\"code\":0,\"data\":[\"mobile\",\"xxxxxxx\"],\"msg\":\"ok\"}"
	resp := []byte(head + "\r\nDate: Thu, 16 Aug 2018 03:10:03 GMT\r\nKeep-Alive: timeout=5, max=100\r\nConnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n" + wantBody)
	reader := remote.NewReaderBuffer(resp)

	// 2. test
	err := skipToBody(reader)
	test.Assert(t, err == nil)

	getBody := make([]byte, reader.ReadableLen())
	_, err = reader.ReadBinary(getBody)
	test.Assert(t, err == nil)
	test.Assert(t, strings.Compare(string(getBody), wantBody) == 0)
}

// TestParseHTTPResponseHead test http_client_handler parseHTTPResponseHead success
func TestParseHTTPResponseHead(t *testing.T) {
	// 1. prepare mock data
	head := "HTTP/1.1 200 OK"

	// 2. test
	major, minor, statusCode, err := parseHTTPResponseHead(head)
	test.Assert(t, err == nil)
	test.Assert(t, !(major != 1 || minor != 1 || statusCode != 200))
}

// TestGetBodyBufReader test http_client_handler getBodyBufReader return  err
func TestGetBodyBufReader(t *testing.T) {
	// 1. prepare mock data
	head := "HTTP/1.1 200 OK"
	body := "{\"code\":0,\"data\":[\"mobile\",\"xxxxxxx\"],\"msg\":\"ok\"}"
	resp := []byte(head + "\r\nDate: Thu, 16 Aug 2018 03:10:03 GMT\r\nKeep-Alive: timeout=5, max=100\r\nConnection: Keep-Alive\r\nTransfer-Encoding: chunked\r\nContent-Type: text/html; charset=UTF-8\r\n\r\n" + body)
	reader := remote.NewReaderBuffer(resp)

	// 2. test
	_, err := getBodyBufReader(reader)
	// check err not nil
	test.Assert(t, err != nil)
}
