// Copyright (c) quickfixengine.org  All rights reserved.
//
// This file may be distributed under the terms of the quickfixengine.org
// license as defined by quickfixengine.org and appearing in the file
// LICENSE included in the packaging of this file.
//
// This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
// THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A
// PARTICULAR PURPOSE.
//
// See http://www.quickfixengine.org/LICENSE for licensing information.
//
// Contact ask@quickfixengine.org if any conditions of this licensing
// are not clear to you.

package quickfix

import (
	"bytes"
	"fmt"
	"testing"

	"github.com/stretchr/testify/suite"
)

type MessageRouterTestSuite struct {
	suite.Suite
	*MessageRouter
	msg             *Message
	sessionID       SessionID
	returnReject    MessageRejectError
	routedBy        string
	routedSessionID SessionID
	routedMessage   *Message
}

func TestMessageRouterTestSuite(t *testing.T) {
	suite.Run(t, new(MessageRouterTestSuite))
}

func (suite *MessageRouterTestSuite) givenTheRoute(beginString, msgType string) {
	suite.AddRoute(
		beginString,
		msgType,
		func(msg *Message, sessionID SessionID) MessageRejectError {
			suite.routedBy = fmt.Sprintf("%v:%v", beginString, msgType)
			suite.routedSessionID = sessionID
			suite.routedMessage = msg

			return suite.returnReject
		},
	)
}

func (suite *MessageRouterTestSuite) givenTheMessage(msgBytes []byte) {
	err := ParseMessage(suite.msg, bytes.NewBuffer(msgBytes))
	suite.Nil(err)

	var beginString FIXString
	suite.Require().Nil(suite.msg.Header.GetField(tagBeginString, &beginString))
	var senderCompID FIXString
	suite.Require().Nil(suite.msg.Header.GetField(tagSenderCompID, &senderCompID))
	var targetCompID FIXString
	suite.Require().Nil(suite.msg.Header.GetField(tagTargetCompID, &targetCompID))
	suite.sessionID = SessionID{BeginString: string(beginString), SenderCompID: string(targetCompID), TargetCompID: string(senderCompID)}
}

func (suite *MessageRouterTestSuite) givenTargetDefaultApplVerIDForSession(defaultApplVerID string, sessionID SessionID) {
	s := &session{
		sessionID:              sessionID,
		targetDefaultApplVerID: defaultApplVerID,
	}
	suite.Nil(registerSession(s))
}

func (suite *MessageRouterTestSuite) givenAFIX42NewOrderSingle() {
	suite.givenTheMessage([]byte("8=FIX.4.29=8735=D49=TW34=356=ISLD52=20160421-14:43:5040=160=20160421-14:43:5054=121=311=id10=235"))
}

func (suite *MessageRouterTestSuite) givenAFIXTLogonMessage() {
	suite.givenTheMessage([]byte("8=FIXT.1.19=6335=A34=149=TW52=20160420-21:21:4956=ISLD98=0108=21137=810=105"))
}

func (suite *MessageRouterTestSuite) anticipateReject(rej MessageRejectError) {
	suite.returnReject = rej
}

func (suite *MessageRouterTestSuite) verifyMessageNotRouted() {
	suite.Equal("", suite.routedBy, "Message should not be routed")
}

func (suite *MessageRouterTestSuite) verifyMessageRoutedBy(beginString, msgType string) {
	suite.NotEqual("", suite.routedBy, "Message expected to be routed")

	suite.Equal(fmt.Sprintf("%v:%v", beginString, msgType), suite.routedBy)
	suite.Equal(suite.sessionID, suite.routedSessionID)
	suite.Equal(suite.msg.String(), suite.routedMessage.String())
}

func (suite *MessageRouterTestSuite) resetRouter() {
	suite.MessageRouter = NewMessageRouter()
	suite.routedBy = ""
	suite.routedSessionID = SessionID{}
	suite.routedMessage = &Message{}
	suite.returnReject = nil
}

func (suite *MessageRouterTestSuite) SetupTest() {
	suite.resetRouter()
	sessionsLock.Lock()
	defer sessionsLock.Unlock()

	sessions = make(map[SessionID]*session)
	suite.msg = NewMessage()
}

func (suite *MessageRouterTestSuite) TestNoRoute() {
	suite.givenTheMessage([]byte("8=FIX.4.39=8735=D49=TW34=356=ISLD52=20160421-14:43:5040=160=20160421-14:43:5054=121=311=id10=235"))

	rej := suite.Route(suite.msg, suite.sessionID)
	suite.verifyMessageNotRouted()
	suite.Equal(NewBusinessMessageRejectError("Unsupported Message Type", 3, nil), rej)
}

func (suite *MessageRouterTestSuite) TestNoRouteWhitelistedMessageTypes() {
	var tests = []string{"0", "A", "1", "2", "3", "4", "5", "j"}

	for _, test := range tests {
		suite.SetupTest()

		msg := fmt.Sprintf("8=FIX.4.39=8735=%v49=TW34=356=ISLD52=20160421-14:43:5040=160=20160421-14:43:5054=121=311=id10=235", test)
		suite.givenTheMessage([]byte(msg))

		rej := suite.Route(suite.msg, suite.sessionID)
		suite.verifyMessageNotRouted()
		suite.Nil(rej, "Message type '%v' should not be rejected by the MessageRouter", test)
	}
}

func (suite *MessageRouterTestSuite) TestSimpleRoute() {
	suite.givenTheRoute(string(BeginStringFIX42), "D")
	suite.givenTheRoute(string(BeginStringFIXT11), "A")

	suite.givenAFIX42NewOrderSingle()
	rej := suite.Route(suite.msg, suite.sessionID)

	suite.verifyMessageRoutedBy(string(BeginStringFIX42), "D")
	suite.Nil(rej)
}

func (suite *MessageRouterTestSuite) TestSimpleRouteWithReject() {
	suite.givenTheRoute(string(BeginStringFIX42), "D")
	suite.givenTheRoute(string(BeginStringFIXT11), "A")
	suite.anticipateReject(NewMessageRejectError("some error", 5, nil))

	suite.givenAFIX42NewOrderSingle()
	rej := suite.Route(suite.msg, suite.sessionID)
	suite.verifyMessageRoutedBy(string(BeginStringFIX42), "D")
	suite.Equal(suite.returnReject, rej)
}

func (suite *MessageRouterTestSuite) TestRouteFIXTAdminMessage() {
	suite.givenTheRoute(string(BeginStringFIX42), "D")
	suite.givenTheRoute(string(BeginStringFIXT11), "A")
	suite.givenAFIXTLogonMessage()

	rej := suite.Route(suite.msg, suite.sessionID)
	suite.verifyMessageRoutedBy(string(BeginStringFIXT11), "A")
	suite.Nil(rej)
}

func (suite *MessageRouterTestSuite) TestRouteFIXT50AppWithApplVerID() {
	suite.givenTheRoute(BeginStringFIX42, "D")
	suite.givenTheRoute(ApplVerIDFIX50, "D")
	suite.givenTheRoute(ApplVerIDFIX50SP1, "D")

	suite.givenTheMessage([]byte("8=FIXT.1.19=8935=D49=TW34=356=ISLD52=20160424-16:48:261128=740=160=20160424-16:48:2611=id21=310=120"))
	rej := suite.Route(suite.msg, suite.sessionID)
	suite.verifyMessageRoutedBy(ApplVerIDFIX50, "D")
	suite.Nil(rej)
}

func (suite *MessageRouterTestSuite) TestRouteFIXTAppWithApplVerID() {
	suite.givenTheRoute(BeginStringFIX42, "D")
	suite.givenTheRoute(ApplVerIDFIX50, "D")
	suite.givenTheRoute(ApplVerIDFIX50SP1, "D")

	suite.givenTheMessage([]byte("8=FIXT.1.19=8935=D49=TW34=356=ISLD52=20160424-16:48:261128=840=160=20160424-16:48:2611=id21=310=120"))
	rej := suite.Route(suite.msg, suite.sessionID)
	suite.verifyMessageRoutedBy(ApplVerIDFIX50SP1, "D")
	suite.Nil(rej)
}

func (suite *MessageRouterTestSuite) TestRouteFIXTAppWithDefaultApplVerID() {
	suite.givenTheRoute(BeginStringFIX42, "D")
	suite.givenTheRoute(ApplVerIDFIX50, "D")
	suite.givenTheRoute(ApplVerIDFIX50SP1, "D")
	suite.givenTargetDefaultApplVerIDForSession(
		"8",
		SessionID{BeginString: string(BeginStringFIXT11), SenderCompID: "ISLD", TargetCompID: "TW"},
	)

	suite.givenTheMessage([]byte("8=FIXT.1.19=8235=D49=TW34=356=ISLD52=20160424-16:48:2640=160=20160424-16:48:2611=id21=310=120"))
	rej := suite.Route(suite.msg, suite.sessionID)
	suite.verifyMessageRoutedBy(ApplVerIDFIX50SP1, "D")
	suite.Nil(rej)
}
