/*
 * 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 thrift

import (
	"reflect"
	"strings"
	"testing"

	"github.com/cloudwego/thriftgo/parser"

	"github.com/cloudwego/kitex/internal/test"
	"github.com/cloudwego/kitex/pkg/generic/descriptor"
	"github.com/cloudwego/kitex/pkg/serviceinfo"
)

var httpIDL = `
namespace go http

struct Item{
    1: optional i64 id(go.tag = "json:\"id\"")
    2: optional string text
}

struct JsonDict{
}

struct ReqItem{
    1: optional i64 id(go.tag = "json:\"id\"")
    2: optional string text
}


struct BizRequest {
    1: optional i64 v_int64(api.query = 'v_int64', api.vd = "$>0&&$<200")
    2: optional string text(api.body = 'text')
    3: optional i32 token(api.header = 'token')
    4: optional JsonDict json_header(api.header = 'json_header')
    5: optional Item some(api.body = 'some')
    6: optional list<ReqItem> req_items(api.query = 'req_items')
    7: optional i32 api_version(api.path = 'action')
    8: optional i64 uid(api.path = 'biz')
    9: optional list<i64> cids(api.query = 'cids')
    10: optional list<string> vids(api.query = 'vids')
    
    11: required string required_filed
}

struct RspItem{
    1: optional i64 item_id 
    2: optional string text
}
struct BizResponse {
    1: optional string T                             (api.header= 'T') 
    2: optional map<i64, RspItem> rsp_items           (api.body='rsp_items')
    3: optional i32 v_enum                       (api.none = '')
    4: optional list<RspItem> rsp_item_list            (api.body = 'rsp_item_list')
    5: optional i32 http_code                         (api.http_code = '') 
    6: optional list<i64> item_count (api.header = 'item_count')
}

exception Exception{
    1: i32 code
    2: string msg
}

service BizService{
BizResponse BizMethod1(1: BizRequest req) throws (1: Exception err) (api.get = '/life/client/:action/:biz', api.baseurl = 'ib.snssdk.com', api.param = 'true') 
BizResponse BizMethod2(1: BizRequest req)(api.post = '/life/client/:action/:biz', api.baseurl = 'ib.snssdk.com', api.param = 'true', api.serializer = 'form')
BizResponse BizMethod3(1: BizRequest req)(api.post = '/life/client/:action/:biz/more', api.baseurl = 'ib.snssdk.com', api.param = 'true', api.serializer = 'json')
}`

func TestParseHttpIDL(t *testing.T) {
	re, err := parser.ParseString("http.thrift", httpIDL)
	test.Assert(t, err == nil, err)
	svc, err := Parse(re, LastServiceOnly)
	test.Assert(t, err == nil)
	test.Assert(t, len(svc.Functions) == 3)
	bizMethod1 := svc.Functions["BizMethod1"]
	test.Assert(t, bizMethod1.Request.Type == descriptor.STRUCT)
	test.Assert(t, len(bizMethod1.Request.Struct.FieldsByName["req"].Type.Struct.RequiredFields) == 1)
	test.Assert(t, bizMethod1.Request.Struct.FieldsByName["req"].Type.Struct.FieldsByName["required_filed"].Required)
	test.Assert(t, len(bizMethod1.Request.Struct.FieldsByID) == 1)
	test.Assert(t, bizMethod1.Response.Type == descriptor.STRUCT)
	test.Assert(t, len(bizMethod1.Response.Struct.FieldsByID) == 2)
}

var httpConflictPathIDL = `
namespace go http

struct BizRequest {
    1: optional i32 api_version(api.path = 'action')
    2: optional i64 uid(api.path = 'biz')
}

struct BizResponse {
    1: optional string T(api.header= 'T')
}

service BizService{
BizResponse BizMethod1(1: BizRequest req)(api.post = '/life/client/:action/:biz/*one', api.baseurl = 'ib.snssdk.com', api.param = 'true', api.serializer = 'form')
BizResponse BizMethod2(1: BizRequest req)(api.post = '/life/client/:action/:biz/*two', api.baseurl = 'ib.snssdk.com', api.param = 'true', api.serializer = 'json')
BizResponse BizMethod3(1: BizRequest req)(api.post = '/life/client/:action/:biz/*three', api.baseurl = 'ib.snssdk.com', api.param = 'true', api.serializer = 'json')
}`

func TestPanicRecover(t *testing.T) {
	re, err := parser.ParseString("http.thrift", httpConflictPathIDL)
	test.Assert(t, err == nil, err)
	_, err = Parse(re, LastServiceOnly)
	test.Assert(t, err != nil)
	test.DeepEqual(t, err.Error(), "router handle failed, err=handlers are already registered for path '/life/client/:action/:biz/*two'")
}

var selfReferenceIDL = `
namespace go http

struct A {
    1: A self
}

service B{
    A Foo(1: A req) 
    string Ping(1: string msg)
}
`

func TestSelfReferenceParse(t *testing.T) {
	re, err := parser.ParseString("a.thrift", selfReferenceIDL)
	test.Assert(t, err == nil, err)
	svc, err := Parse(re, LastServiceOnly)
	test.Assert(t, err == nil, err)
	for _, fn := range svc.Functions {
		for _, i := range fn.Request.Struct.FieldsByID {
			_ = i
		}
	}
	_ = svc
}

var notSupportAnnotationIDL = `
namespace go http

struct A {
    1: A self (whoami = '')
}

service B{
    A Foo(1: A req)
    string Ping(1: string msg)
}
`

type notSupportAnnotation struct{}

func (notSupportAnnotation) Equal(key, value string) bool {
	return key == "whoami"
}

func (notSupportAnnotation) Handle() interface{} {
	return 1
}

func TestNotSupportAnnotation(t *testing.T) {
	descriptor.RegisterAnnotation(notSupportAnnotation{})
	re, err := parser.ParseString("a.thrift", notSupportAnnotationIDL)
	test.Assert(t, err == nil)
	_, err = Parse(re, LastServiceOnly)
	test.Assert(t, err != nil)
}

var multiServicesIDL = `
namespace go test

service A {
	string method1(1: string req)
}

service B {
	string method2(1: string req)
}
`

func TestCombineService(t *testing.T) {
	content := multiServicesIDL
	tree, err := parser.ParseString("a.thrift", content)
	test.Assert(t, err == nil)

	dp, err := Parse(tree, LastServiceOnly)
	test.Assert(t, err == nil)
	test.Assert(t, dp.Name == "B")
	test.Assert(t, len(dp.Functions) == 1 && dp.Functions["method2"] != nil)

	dp, err = Parse(tree, CombineServices)
	test.Assert(t, err == nil)
	test.Assert(t, dp.Name == "CombinedServices")
	test.Assert(t, len(dp.Functions) == 2 && dp.Functions["method1"] != nil && dp.Functions["method2"] != nil)

	content = strings.ReplaceAll(content, "method2", "method1")
	tree, err = parser.ParseString("a.thrift", content)
	test.Assert(t, err == nil)

	dp, err = Parse(tree, CombineServices)
	test.Assert(t, err != nil && dp == nil)
	test.Assert(t, err.Error() == "duplicate method name: method1")
}

const baseIDL = `
namespace * base

struct BaseStruct {
    1: string def
    2: required string req
    3: optional string opt
}

service BaseService {
    BaseStruct simple(1: BaseStruct req)
}
`

const demoIDL = `
namespace * demo

include "base.thrift"

struct Request {
    1: string str
    2: required string str2
    3: optional string str3
}

struct Response {
    1: string str
    2: required string str2
    3: optional string str3
}

exception Exception {
    1: string error
    2: required string error2
    3: optional string error3
}

service DemoBaseService extends base.BaseService {
    binary int2bin(1: i32 arg);
}

service DemoService extends DemoBaseService {
    Response req2res(1: Request req);
}

`

func TestExtends(t *testing.T) {
	demo, err := parser.ParseString("demo.thrift", demoIDL)
	test.Assert(t, err == nil)

	base, err := parser.ParseString("base.thrift", baseIDL)
	test.Assert(t, err == nil)

	demo.Includes[0].Reference = base

	dp, err := Parse(demo, LastServiceOnly)
	test.Assert(t, err == nil)
	test.Assert(t, dp.Name == "DemoService")
	test.Assert(t, len(dp.Functions) == 3)
	for _, fn := range []string{"simple", "int2bin", "req2res"} {
		test.Assert(t, dp.Functions[fn] != nil)
	}

	dp, err = Parse(demo, FirstServiceOnly)
	test.Assert(t, err == nil)
	test.Assert(t, dp.Name == "DemoBaseService")
	test.Assert(t, len(dp.Functions) == 2)
	for _, fn := range []string{"simple", "int2bin"} {
		test.Assert(t, dp.Functions[fn] != nil)
	}

	dp, err = Parse(demo, CombineServices)
	test.Assert(t, err == nil)
	test.Assert(t, dp.Name == "CombinedServices")
	test.Assert(t, len(dp.Functions) == 3)
	for _, fn := range []string{"simple", "int2bin", "req2res"} {
		test.Assert(t, dp.Functions[fn] != nil)
	}
}

const defaultValueBaseIDL = `
namespace * base

enum TestEnum {
    FIRST = 1,
    SECOND = 2,
    THIRD = 3,
    FOURTH = 4,
}

struct BaseElem {
	1: optional i32 a,
}

const BaseElem defaultBaseElem = {
	"a": 56
}

struct BaseStruct {
    1: optional bool a,
    2: optional byte b,
    3: optional i16 c,
	4: optional i32 d,
	5: optional i64 e,
	6: optional double f,
	7: optional string g,
	8: optional binary h,
	9: optional TestEnum i,
	10: optional set<string> j,
	11: optional list<BaseElem> k,
	12: optional map<string, i32> l,
	13: optional map<i32, BaseElem> m,
	14: optional BaseElem n,
}

const set<string> defaultJ = ["123","456"]

const map<string, i32> defaultL = {
	"123": 12,
	"456": 45,
}

const BaseStruct defaultBase = {
	"a": true,
	"b": 1,
	"c": 2,
	"d": 3,
	"e": 4,
	"f": 5.1,
	"g": "123",
	"h": "456",
	"i": TestEnum.THIRD,
	"j": defaultJ,
	"k": [{"a": 34}, defaultBaseElem],
	"l": defaultL,
	"m": {
		12: {
			"a": 34,
		},
	},
	"n": {
		"a": 56,
	},
}
`

const defaultValueDemoIDL = `
namespace * demo

include "base.thrift"

const byte DefaultB = 1
const i16 DefaultC = 2
const i32 DefaultD = 3
const i64 DefaultE = 4

struct Request {
	1: optional bool a = true,
	2: optional byte b = DefaultB,
	3: optional i16 c = DefaultC,
	4: optional i32 d = DefaultD,
	5: optional i64 e = DefaultE,
	6: optional double f = 5.1,
	7: optional string g = "123",
	8: optional binary h = "456",
	9: optional base.TestEnum i = base.TestEnum.THIRD,
	10: optional set<string> j = base.defaultJ,
	11: optional list<base.BaseElem> k = [{"a": 34}, base.defaultBaseElem],
	12: optional map<string, i32> l = base.defaultL,
	13: optional map<i32, base.BaseElem> m = {
		12: {
			"a": 34,
		},
	},
	14: optional base.BaseElem n = {
		"a": 56,
	},
	15: optional base.BaseStruct base = base.defaultBase,
	16: optional list<base.BaseStruct> bases = [base.defaultBase],
}

struct Response {
}

service DemoService {
    Response req2res(1: Request req);
}
`

const streamingIDL = `
namespace go echo

struct Request {
    1: optional string message,
}

struct Response {
    1: optional string message,
}

service TestService {
    Response EchoBidirectional (1: Request req) (streaming.mode="bidirectional"),
    Response EchoClient (1: Request req) (streaming.mode="client"),
    Response EchoServer (1: Request req) (streaming.mode="server"),
	Response EchoUnary (1: Request req) (streaming.mode="unary"),
}
`

func TestDefaultValue(t *testing.T) {
	demo, err := parser.ParseString("demo.thrift", defaultValueDemoIDL)
	test.Assert(t, err == nil)

	base, err := parser.ParseString("base.thrift", defaultValueBaseIDL)
	test.Assert(t, err == nil)

	demo.Includes[0].Reference = base

	dp, err := Parse(demo, DefaultParseMode())
	test.Assert(t, err == nil, err)

	fun, err := dp.LookupFunctionByMethod("req2res")
	test.Assert(t, err == nil)

	defaultValueDeepEqual(t, func(name string) interface{} {
		return fun.Request.Struct.FieldsByName["req"].Type.Struct.FieldsByName[name].DefaultValue
	})

	defaultBase := fun.Request.Struct.FieldsByName["req"].Type.Struct.FieldsByName["base"].DefaultValue.(map[string]interface{})
	defaultValueDeepEqual(t, func(name string) interface{} {
		return defaultBase[name]
	})

	defaultBases := fun.Request.Struct.FieldsByName["req"].Type.Struct.FieldsByName["bases"].DefaultValue.([]interface{})
	for i := range defaultBases {
		defaultBase := defaultBases[i].(map[string]interface{})
		defaultValueDeepEqual(t, func(name string) interface{} {
			return defaultBase[name]
		})
	}
}

func TestStreamingMode(t *testing.T) {
	streaming, err := parser.ParseString("streaming.thrift", streamingIDL)
	test.Assert(t, err == nil)

	dp, err := Parse(streaming, DefaultParseMode())
	test.Assert(t, err == nil)

	method, err := dp.LookupFunctionByMethod("EchoBidirectional")
	test.Assert(t, err == nil)
	test.Assert(t, method.StreamingMode == serviceinfo.StreamingBidirectional)

	method, err = dp.LookupFunctionByMethod("EchoClient")
	test.Assert(t, err == nil)
	test.Assert(t, method.StreamingMode == serviceinfo.StreamingClient)

	method, err = dp.LookupFunctionByMethod("EchoServer")
	test.Assert(t, err == nil)
	test.Assert(t, method.StreamingMode == serviceinfo.StreamingServer)

	method, err = dp.LookupFunctionByMethod("EchoUnary")
	test.Assert(t, err == nil)
	test.Assert(t, method.StreamingMode == serviceinfo.StreamingUnary)
}

func defaultValueDeepEqual(t *testing.T, defaultValue func(name string) interface{}) {
	test.Assert(t, defaultValue("a") == true)
	test.Assert(t, defaultValue("b") == byte(1))
	test.Assert(t, defaultValue("c") == int16(2))
	test.Assert(t, defaultValue("d") == int32(3))
	test.Assert(t, defaultValue("e") == int64(4))
	test.Assert(t, defaultValue("f") == 5.1)
	test.Assert(t, defaultValue("g") == "123")
	test.Assert(t, reflect.DeepEqual(defaultValue("h"), []byte("456")))
	test.Assert(t, defaultValue("i") == int64(3))
	test.Assert(t, reflect.DeepEqual(defaultValue("j"), []interface{}{"123", "456"}))
	test.Assert(t, reflect.DeepEqual(defaultValue("k"), []interface{}{
		map[string]interface{}{"a": int32(34)},
		map[string]interface{}{"a": int32(56)},
	}))
	test.Assert(t, reflect.DeepEqual(defaultValue("l"), map[interface{}]interface{}{
		"123": int32(12),
		"456": int32(45),
	}))
	test.Assert(t, reflect.DeepEqual(defaultValue("m"), map[interface{}]interface{}{
		int32(12): map[string]interface{}{"a": int32(34)},
	}))
	test.Assert(t, reflect.DeepEqual(defaultValue("n"), map[string]interface{}{
		"a": int32(56),
	}))
}

func TestParseWithIDLServiceName(t *testing.T) {
	demo, err := parser.ParseString("demo.thrift", demoIDL)
	test.Assert(t, err == nil)

	base, err := parser.ParseString("base.thrift", baseIDL)
	test.Assert(t, err == nil)

	demo.Includes[0].Reference = base

	sDsc, err := Parse(demo, LastServiceOnly, WithIDLServiceName("DemoBaseService"))
	test.Assert(t, err == nil)
	// priority: service name specification > parse mode
	test.Assert(t, sDsc.Name == "DemoBaseService")
}
