package database

import (
	"testing"
	"time"

	"github.com/gotify/server/v2/model"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func (s *DatabaseSuite) TestMessage() {
	messages, err := s.db.GetMessageByID(5)
	require.NoError(s.T(), err)
	assert.Nil(s.T(), messages, "not existing message")

	user := &model.User{Name: "test", Pass: []byte{1}}
	s.db.CreateUser(user)
	assert.NotEqual(s.T(), 0, user.ID)

	backupServer := &model.Application{UserID: user.ID, Token: "A0000000000", Name: "backupserver"}
	s.db.CreateApplication(backupServer)
	assert.NotEqual(s.T(), 0, backupServer.ID)

	msgs, err := s.db.GetMessagesByUser(user.ID)
	require.NoError(s.T(), err)
	assert.Empty(s.T(), msgs)

	msgs, err = s.db.GetMessagesByApplication(backupServer.ID)
	require.NoError(s.T(), err)
	assert.Empty(s.T(), msgs)

	backupdone := &model.Message{ApplicationID: backupServer.ID, Message: "backup done", Title: "backup", Priority: 1, Date: time.Now()}
	require.NoError(s.T(), s.db.CreateMessage(backupdone))
	assert.NotEqual(s.T(), 0, backupdone.ID)

	messages, err = s.db.GetMessageByID(backupdone.ID)
	require.NoError(s.T(), err)
	assertEquals(s.T(), messages, backupdone)

	msgs, err = s.db.GetMessagesByUser(user.ID)
	require.NoError(s.T(), err)
	assert.Len(s.T(), msgs, 1)
	assertEquals(s.T(), msgs[0], backupdone)

	msgs, err = s.db.GetMessagesByApplication(backupServer.ID)
	require.NoError(s.T(), err)
	assert.Len(s.T(), msgs, 1)
	assertEquals(s.T(), msgs[0], backupdone)

	loginServer := &model.Application{UserID: user.ID, Token: "A0000000001", Name: "loginserver"}
	require.NoError(s.T(), s.db.CreateApplication(loginServer))
	assert.NotEqual(s.T(), 0, loginServer.ID)

	logindone := &model.Message{ApplicationID: loginServer.ID, Message: "login done", Title: "login", Priority: 1, Date: time.Now()}
	require.NoError(s.T(), s.db.CreateMessage(logindone))
	assert.NotEqual(s.T(), 0, logindone.ID)

	msgs, err = s.db.GetMessagesByUser(user.ID)
	require.NoError(s.T(), err)
	assert.Len(s.T(), msgs, 2)
	assertEquals(s.T(), msgs[0], logindone)
	assertEquals(s.T(), msgs[1], backupdone)

	msgs, err = s.db.GetMessagesByApplication(backupServer.ID)
	require.NoError(s.T(), err)
	assert.Len(s.T(), msgs, 1)
	assertEquals(s.T(), msgs[0], backupdone)

	loginfailed := &model.Message{ApplicationID: loginServer.ID, Message: "login failed", Title: "login", Priority: 1, Date: time.Now()}
	require.NoError(s.T(), s.db.CreateMessage(loginfailed))
	assert.NotEqual(s.T(), 0, loginfailed.ID)

	msgs, err = s.db.GetMessagesByApplication(backupServer.ID)
	require.NoError(s.T(), err)
	assert.Len(s.T(), msgs, 1)
	assertEquals(s.T(), msgs[0], backupdone)

	msgs, err = s.db.GetMessagesByApplication(loginServer.ID)
	require.NoError(s.T(), err)
	assert.Len(s.T(), msgs, 2)
	assertEquals(s.T(), msgs[0], loginfailed)
	assertEquals(s.T(), msgs[1], logindone)

	msgs, err = s.db.GetMessagesByUser(user.ID)
	require.NoError(s.T(), err)
	assert.Len(s.T(), msgs, 3)
	assertEquals(s.T(), msgs[0], loginfailed)
	assertEquals(s.T(), msgs[1], logindone)
	assertEquals(s.T(), msgs[2], backupdone)

	backupfailed := &model.Message{ApplicationID: backupServer.ID, Message: "backup failed", Title: "backup", Priority: 1, Date: time.Now()}
	require.NoError(s.T(), s.db.CreateMessage(backupfailed))
	assert.NotEqual(s.T(), 0, backupfailed.ID)

	msgs, err = s.db.GetMessagesByUser(user.ID)
	require.NoError(s.T(), err)
	assert.Len(s.T(), msgs, 4)
	assertEquals(s.T(), msgs[0], backupfailed)
	assertEquals(s.T(), msgs[1], loginfailed)
	assertEquals(s.T(), msgs[2], logindone)
	assertEquals(s.T(), msgs[3], backupdone)

	msgs, err = s.db.GetMessagesByApplication(loginServer.ID)
	require.NoError(s.T(), err)
	assert.Len(s.T(), msgs, 2)
	assertEquals(s.T(), msgs[0], loginfailed)
	assertEquals(s.T(), msgs[1], logindone)

	require.NoError(s.T(), s.db.DeleteMessagesByApplication(loginServer.ID))
	msgs, err = s.db.GetMessagesByApplication(loginServer.ID)
	require.NoError(s.T(), err)
	assert.Empty(s.T(), msgs)

	msgs, err = s.db.GetMessagesByUser(user.ID)
	require.NoError(s.T(), err)
	assert.Len(s.T(), msgs, 2)
	assertEquals(s.T(), msgs[0], backupfailed)
	assertEquals(s.T(), msgs[1], backupdone)

	logindone = &model.Message{ApplicationID: loginServer.ID, Message: "login done", Title: "login", Priority: 1, Date: time.Now()}
	require.NoError(s.T(), s.db.CreateMessage(logindone))
	assert.NotEqual(s.T(), 0, logindone.ID)

	msgs, err = s.db.GetMessagesByUser(user.ID)
	require.NoError(s.T(), err)
	assert.Len(s.T(), msgs, 3)
	assertEquals(s.T(), msgs[0], logindone)
	assertEquals(s.T(), msgs[1], backupfailed)
	assertEquals(s.T(), msgs[2], backupdone)

	s.db.DeleteMessagesByUser(user.ID)
	msgs, err = s.db.GetMessagesByUser(user.ID)
	require.NoError(s.T(), err)
	assert.Empty(s.T(), msgs)

	logout := &model.Message{ApplicationID: loginServer.ID, Message: "logout success", Title: "logout", Priority: 1, Date: time.Now()}
	s.db.CreateMessage(logout)
	msgs, err = s.db.GetMessagesByUser(user.ID)
	require.NoError(s.T(), err)
	assert.Len(s.T(), msgs, 1)
	assertEquals(s.T(), msgs[0], logout)

	require.NoError(s.T(), s.db.DeleteMessageByID(logout.ID))
	msgs, err = s.db.GetMessagesByUser(user.ID)
	require.NoError(s.T(), err)
	assert.Empty(s.T(), msgs)

}

func (s *DatabaseSuite) TestGetMessagesSince() {
	user := &model.User{Name: "test", Pass: []byte{1}}
	require.NoError(s.T(), s.db.CreateUser(user))

	app := &model.Application{UserID: user.ID, Token: "A0000000000"}
	app2 := &model.Application{UserID: user.ID, Token: "A0000000001"}
	require.NoError(s.T(), s.db.CreateApplication(app))
	require.NoError(s.T(), s.db.CreateApplication(app2))

	curDate := time.Now()
	for i := 1; i <= 500; i++ {
		s.db.CreateMessage(&model.Message{ApplicationID: app.ID, Message: "abc", Date: curDate.Add(time.Duration(i) * time.Second)})
		s.db.CreateMessage(&model.Message{ApplicationID: app2.ID, Message: "abc", Date: curDate.Add(time.Duration(i) * time.Second)})
	}

	actual, err := s.db.GetMessagesByUserSince(user.ID, 50, 0)
	require.NoError(s.T(), err)
	assert.Len(s.T(), actual, 50)
	hasIDInclusiveBetween(s.T(), actual, 1000, 951, 1)

	actual, err = s.db.GetMessagesByUserSince(user.ID, 50, 951)
	require.NoError(s.T(), err)
	assert.Len(s.T(), actual, 50)
	hasIDInclusiveBetween(s.T(), actual, 950, 901, 1)

	actual, err = s.db.GetMessagesByUserSince(user.ID, 100, 951)
	require.NoError(s.T(), err)
	assert.Len(s.T(), actual, 100)
	hasIDInclusiveBetween(s.T(), actual, 950, 851, 1)

	actual, err = s.db.GetMessagesByUserSince(user.ID, 100, 51)
	require.NoError(s.T(), err)
	assert.Len(s.T(), actual, 50)
	hasIDInclusiveBetween(s.T(), actual, 50, 1, 1)

	actual, err = s.db.GetMessagesByApplicationSince(app.ID, 50, 0)
	require.NoError(s.T(), err)
	assert.Len(s.T(), actual, 50)
	hasIDInclusiveBetween(s.T(), actual, 999, 901, 2)

	actual, err = s.db.GetMessagesByApplicationSince(app.ID, 50, 901)
	require.NoError(s.T(), err)
	assert.Len(s.T(), actual, 50)
	hasIDInclusiveBetween(s.T(), actual, 899, 801, 2)

	actual, err = s.db.GetMessagesByApplicationSince(app.ID, 100, 666)
	require.NoError(s.T(), err)
	assert.Len(s.T(), actual, 100)
	hasIDInclusiveBetween(s.T(), actual, 665, 467, 2)

	actual, err = s.db.GetMessagesByApplicationSince(app.ID, 100, 101)
	require.NoError(s.T(), err)
	assert.Len(s.T(), actual, 50)
	hasIDInclusiveBetween(s.T(), actual, 99, 1, 2)

	actual, err = s.db.GetMessagesByApplicationSince(app2.ID, 50, 0)
	require.NoError(s.T(), err)
	assert.Len(s.T(), actual, 50)
	hasIDInclusiveBetween(s.T(), actual, 1000, 902, 2)

	actual, err = s.db.GetMessagesByApplicationSince(app2.ID, 50, 902)
	require.NoError(s.T(), err)
	assert.Len(s.T(), actual, 50)
	hasIDInclusiveBetween(s.T(), actual, 900, 802, 2)

	actual, err = s.db.GetMessagesByApplicationSince(app2.ID, 100, 667)
	require.NoError(s.T(), err)
	assert.Len(s.T(), actual, 100)
	hasIDInclusiveBetween(s.T(), actual, 666, 468, 2)

	actual, err = s.db.GetMessagesByApplicationSince(app2.ID, 100, 102)
	require.NoError(s.T(), err)
	assert.Len(s.T(), actual, 50)
	hasIDInclusiveBetween(s.T(), actual, 100, 2, 2)

}

func hasIDInclusiveBetween(t *testing.T, msgs []*model.Message, from, to, decrement int) {
	index := 0
	for expectedID := from; expectedID >= to; expectedID -= decrement {
		if !assert.Equal(t, uint(expectedID), msgs[index].ID) {
			break
		}
		index++
	}
	assert.Equal(t, index, len(msgs), "not all entries inside msgs were checked")
}

// assertEquals compares messages and correctly check dates
func assertEquals(t *testing.T, left *model.Message, right *model.Message) {
	assert.Equal(t, left.Date.Unix(), right.Date.Unix())
	left.Date = right.Date
	assert.Equal(t, left, right)
}
