// 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 (
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/stretchr/testify/suite"

	"quantex.com/qfixpt/quickfix/config"
)

func TestSettings_New(t *testing.T) {
	s := NewSettings()
	assert.NotNil(t, s)

	globalSettings := s.GlobalSettings()
	assert.NotNil(t, globalSettings)

	sessionSettings := s.SessionSettings()
	assert.NotNil(t, sessionSettings)
	assert.Empty(t, sessionSettings)
}

type SettingsAddSessionSuite struct {
	suite.Suite
	settings *Settings
}

func TestSettingsAddSessionSuite(t *testing.T) {
	suite.Run(t, new(SettingsAddSessionSuite))
}

func (s *SettingsAddSessionSuite) SetupTest() {
	s.settings = NewSettings()
}

func (s *SettingsAddSessionSuite) TestBeginStringValidation() {
	ss := NewSessionSettings()
	ss.Set(config.SenderCompID, "CB")
	ss.Set(config.TargetCompID, "SS")

	_, err := s.settings.AddSession(ss)
	s.NotNil(err)

	ss.Set(config.BeginString, "NotAValidBeginString")
	_, err = s.settings.AddSession(ss)
	s.NotNil(err)

	var cases = []string{
		BeginStringFIX40,
		BeginStringFIX41,
		BeginStringFIX42,
		BeginStringFIX43,
		BeginStringFIX44,
		BeginStringFIXT11,
	}

	for _, beginString := range cases {
		ss.Set(config.BeginString, beginString)
		sid, err := s.settings.AddSession(ss)
		s.Nil(err)
		s.Equal(sid, SessionID{BeginString: beginString, SenderCompID: "CB", TargetCompID: "SS"})
	}
}

func (s *SettingsAddSessionSuite) TestGlobalOverlay() {
	globalSettings := s.settings.GlobalSettings()
	globalSettings.Set(config.BeginString, "FIX.4.0")
	globalSettings.Set(config.SocketAcceptPort, "1000")

	s1 := NewSessionSettings()
	s1.Set(config.BeginString, "FIX.4.1")
	s1.Set(config.SenderCompID, "CB")
	s1.Set(config.TargetCompID, "SS")

	s2 := NewSessionSettings()
	s2.Set(config.ResetOnLogon, "Y")
	s2.Set(config.SenderCompID, "CB")
	s2.Set(config.TargetCompID, "SS")

	sessionID1 := SessionID{BeginString: "FIX.4.1", SenderCompID: "CB", TargetCompID: "SS"}
	sessionID2 := SessionID{BeginString: "FIX.4.0", SenderCompID: "CB", TargetCompID: "SS"}

	var addCases = []struct {
		settings          *SessionSettings
		expectedSessionID SessionID
	}{
		{s1, sessionID1},
		{s2, sessionID2},
	}

	for _, tc := range addCases {
		sid, err := s.settings.AddSession(tc.settings)
		s.Nil(err)
		s.Equal(sid, tc.expectedSessionID)
	}

	var cases = []struct {
		sessionID SessionID
		input     string
		expected  string
	}{
		{sessionID1, config.BeginString, "FIX.4.1"},
		{sessionID1, config.SocketAcceptPort, "1000"},
		{sessionID2, config.BeginString, "FIX.4.0"},
		{sessionID2, config.SocketAcceptPort, "1000"},
		{sessionID2, config.ResetOnLogon, "Y"},
	}

	sessionSettings := s.settings.SessionSettings()
	s.Len(sessionSettings, 2)
	for _, tc := range cases {
		settings := sessionSettings[tc.sessionID]

		actual, err := settings.Setting(tc.input)
		s.Nil(err)
		s.Equal(actual, tc.expected)
	}
}

func (s *SettingsAddSessionSuite) TestRejectDuplicate() {
	s1 := NewSessionSettings()
	s1.Set(config.BeginString, "FIX.4.1")
	s1.Set(config.SenderCompID, "CB")
	s1.Set(config.TargetCompID, "SS")

	s2 := NewSessionSettings()
	s2.Set(config.BeginString, "FIX.4.0")
	s2.Set(config.SenderCompID, "CB")
	s2.Set(config.TargetCompID, "SS")

	_, err := s.settings.AddSession(s1)
	s.Nil(err)
	_, err = s.settings.AddSession(s2)
	s.Nil(err)

	s3 := NewSessionSettings()
	s1.Set(config.BeginString, "FIX.4.0")
	s3.Set(config.SenderCompID, "CB")
	s3.Set(config.TargetCompID, "SS")
	_, err = s.settings.AddSession(s3)
	s.NotNil(err, "Expected error for adding duplicate session")

	sessionSettings := s.settings.SessionSettings()
	s.Len(sessionSettings, 2)
}

func TestSettings_ParseSettings(t *testing.T) {
	cfg := `
# default settings for sessions
[DEFAULT]
ConnectionType=initiator
ReconnectInterval=60
SenderCompID=TW


# session definition
[SESSION]
# inherit ConnectionType, ReconnectInterval and SenderCompID from default

BeginString=FIX.4.1
TargetCompID=ARCA
StartTime=12:30:00
EndTime=23:30:00
HeartBtInt=20
SocketConnectPort=9823
SocketConnectHost=123.123.123.123
DataDictionary=somewhere/FIX41.xml


[SESSION]
BeginString=FIX.4.0
TargetCompID=ISLD
StartTime=12:00:00
EndTime=23:00:00
HeartBtInt=30
SocketConnectPort=8323
SocketConnectHost=23.23.23.23
DataDictionary=somewhere/FIX40.xml


[SESSION]
BeginString=FIX.4.2
SenderSubID=TWSub
SenderLocationID=TWLoc
TargetCompID=INCA
TargetSubID=INCASub
TargetLocationID=INCALoc
StartTime=12:30:00
EndTime=21:30:00
# overide default setting for RecconnectInterval
ReconnectInterval=30
HeartBtInt=30
SocketConnectPort=6523
SocketConnectHost=3.3.3.3
# (optional) alternate connection ports and hosts to cycle through on failover
SocketConnectPort1=8392
SocketConnectHost1=8.8.8.8
SocketConnectPort2=2932
SocketConnectHost2=12.12.12.12
DataDictionary=somewhere/FIX42.xml
`

	stringReader := strings.NewReader(cfg)
	s, err := ParseSettings(stringReader)
	assert.Nil(t, err)
	assert.NotNil(t, s)

	var globalTCs = []struct {
		setting  string
		expected string
	}{
		{"ConnectionType", "initiator"},
		{"ReconnectInterval", "60"},
		{"SenderCompID", "TW"},
	}

	globalSettings := s.GlobalSettings()
	for _, tc := range globalTCs {
		actual, err := globalSettings.Setting(tc.setting)
		assert.Nil(t, err)

		assert.Equal(t, tc.expected, actual)
	}

	sessionSettings := s.SessionSettings()
	assert.Len(t, sessionSettings, 3)

	sessionID1 := SessionID{BeginString: "FIX.4.1", SenderCompID: "TW", TargetCompID: "ARCA"}
	sessionID2 := SessionID{BeginString: "FIX.4.0", SenderCompID: "TW", TargetCompID: "ISLD"}
	sessionID3 := SessionID{
		BeginString:  "FIX.4.2",
		SenderCompID: "TW", SenderSubID: "TWSub", SenderLocationID: "TWLoc",
		TargetCompID: "INCA", TargetSubID: "INCASub", TargetLocationID: "INCALoc"}

	var sessionTCs = []struct {
		sessionID SessionID
		setting   string
		expected  string
	}{
		{sessionID1, "ConnectionType", "initiator"},
		{sessionID1, "ReconnectInterval", "60"},
		{sessionID1, "SenderCompID", "TW"},
		{sessionID1, "BeginString", "FIX.4.1"},
		{sessionID1, "TargetCompID", "ARCA"},
		{sessionID1, "StartTime", "12:30:00"},
		{sessionID1, "EndTime", "23:30:00"},
		{sessionID1, "HeartBtInt", "20"},
		{sessionID1, "SocketConnectPort", "9823"},
		{sessionID1, "SocketConnectHost", "123.123.123.123"},
		{sessionID1, "DataDictionary", "somewhere/FIX41.xml"},

		{sessionID2, "ConnectionType", "initiator"},
		{sessionID2, "ReconnectInterval", "60"},
		{sessionID2, "SenderCompID", "TW"},
		{sessionID2, "BeginString", "FIX.4.0"},
		{sessionID2, "TargetCompID", "ISLD"},
		{sessionID2, "StartTime", "12:00:00"},
		{sessionID2, "EndTime", "23:00:00"},
		{sessionID2, "HeartBtInt", "30"},
		{sessionID2, "SocketConnectPort", "8323"},
		{sessionID2, "SocketConnectHost", "23.23.23.23"},
		{sessionID2, "DataDictionary", "somewhere/FIX40.xml"},

		{sessionID3, "ConnectionType", "initiator"},
		{sessionID3, "BeginString", "FIX.4.2"},
		{sessionID3, "SenderCompID", "TW"},
		{sessionID3, "TargetCompID", "INCA"},
		{sessionID3, "StartTime", "12:30:00"},
		{sessionID3, "EndTime", "21:30:00"},
		{sessionID3, "ReconnectInterval", "30"},
		{sessionID3, "HeartBtInt", "30"},
		{sessionID3, "SocketConnectPort", "6523"},
		{sessionID3, "SocketConnectHost", "3.3.3.3"},
		{sessionID3, "SocketConnectPort1", "8392"},
		{sessionID3, "SocketConnectHost1", "8.8.8.8"},
		{sessionID3, "SocketConnectPort2", "2932"},
		{sessionID3, "SocketConnectHost2", "12.12.12.12"},
		{sessionID3, "DataDictionary", "somewhere/FIX42.xml"},
	}

	for _, tc := range sessionTCs {
		settings, ok := sessionSettings[tc.sessionID]
		require.True(t, ok, "No Session recalled for %v", tc.sessionID)
		actual, err := settings.Setting(tc.setting)

		assert.Nil(t, err)
		assert.Equal(t, tc.expected, actual)
	}
}

func TestSettings_ParseSettings_WithEqualsSignInValue(t *testing.T) {
	s, err := ParseSettings(strings.NewReader(`
[DEFAULT]
ConnectionType=initiator
SQLDriver=mysql
SQLDataSourceName=root:root@/quickfix?parseTime=true&loc=UTC

[SESSION]
BeginString=FIX.4.2
SenderCompID=SENDER
TargetCompID=TARGET`))

	require.Nil(t, err)
	require.NotNil(t, s)

	sessionSettings := s.SessionSettings()[SessionID{BeginString: "FIX.4.2", SenderCompID: "SENDER", TargetCompID: "TARGET"}]
	val, err := sessionSettings.Setting("SQLDataSourceName")
	assert.Nil(t, err)
	assert.Equal(t, `root:root@/quickfix?parseTime=true&loc=UTC`, val)
}

func TestSettings_SessionIDFromSessionSettings(t *testing.T) {
	var testCases = []struct {
		globalBeginString   string
		globalTargetCompID  string
		globalSenderCompID  string
		sessionBeginString  string
		sessionTargetCompID string
		sessionSenderCompID string
		sessionQualifier    string
		expectedSessionID   SessionID
	}{
		{globalBeginString: "FIX.4.0", globalTargetCompID: "CB", globalSenderCompID: "SS",
			expectedSessionID: SessionID{BeginString: "FIX.4.0", TargetCompID: "CB", SenderCompID: "SS"}},

		{sessionBeginString: "FIX.4.1", sessionTargetCompID: "GE", sessionSenderCompID: "LE",
			expectedSessionID: SessionID{BeginString: "FIX.4.1", TargetCompID: "GE", SenderCompID: "LE"}},

		{globalBeginString: "FIX.4.2", globalTargetCompID: "CB", sessionTargetCompID: "GE", sessionSenderCompID: "LE", sessionQualifier: "J",
			expectedSessionID: SessionID{BeginString: "FIX.4.2", TargetCompID: "GE", SenderCompID: "LE", Qualifier: "J"}},
	}

	for _, tc := range testCases {
		globalSettings := NewSessionSettings()
		sessionSettings := NewSessionSettings()

		if tc.globalBeginString != "" {
			globalSettings.Set(config.BeginString, tc.globalBeginString)
		}

		if tc.sessionBeginString != "" {
			sessionSettings.Set(config.BeginString, tc.sessionBeginString)
		}

		if tc.globalTargetCompID != "" {
			globalSettings.Set(config.TargetCompID, tc.globalTargetCompID)
		}

		if tc.sessionTargetCompID != "" {
			sessionSettings.Set(config.TargetCompID, tc.sessionTargetCompID)
		}

		if tc.globalSenderCompID != "" {
			globalSettings.Set(config.SenderCompID, tc.globalSenderCompID)
		}

		if tc.sessionSenderCompID != "" {
			sessionSettings.Set(config.SenderCompID, tc.sessionSenderCompID)
		}

		if len(tc.sessionQualifier) > 0 {
			sessionSettings.Set(config.SessionQualifier, tc.sessionQualifier)
		}

		actualSessionID := sessionIDFromSessionSettings(globalSettings, sessionSettings)

		if tc.expectedSessionID != actualSessionID {
			t.Errorf("Expected %v, got %v", tc.expectedSessionID, actualSessionID)
		}
	}
}
