package smtpd

import (
	"fmt"
	"io"
	"net/mail"
	"testing"
	"time"

	"github.com/jhillyerd/go.enmime"
	"github.com/stretchr/testify/mock"
)

func TestDoRetentionScan(t *testing.T) {
	// Create mock objects
	mds := &MockDataStore{}

	mb1 := &MockMailbox{}
	mb2 := &MockMailbox{}
	mb3 := &MockMailbox{}

	// Mockup some different aged messages (num is in hours)
	new1 := mockMessage(0)
	new2 := mockMessage(1)
	new3 := mockMessage(2)
	old1 := mockMessage(4)
	old2 := mockMessage(12)
	old3 := mockMessage(24)

	// First it should ask for all mailboxes
	mds.On("AllMailboxes").Return([]Mailbox{mb1, mb2, mb3}, nil)

	// Then for all messages on each box
	mb1.On("GetMessages").Return([]Message{new1, old1, old2}, nil)
	mb2.On("GetMessages").Return([]Message{old3, new2}, nil)
	mb3.On("GetMessages").Return([]Message{new3}, nil)

	// Test 4 hour retention
	if err := doRetentionScan(mds, 4*time.Hour, 0); err != nil {
		t.Error(err)
	}

	// Check our assertions
	mds.AssertExpectations(t)
	mb1.AssertExpectations(t)
	mb2.AssertExpectations(t)
	mb3.AssertExpectations(t)

	// Delete should not have been called on new messages
	new1.AssertNotCalled(t, "Delete")
	new2.AssertNotCalled(t, "Delete")
	new3.AssertNotCalled(t, "Delete")

	// Delete should have been called once on old messages
	old1.AssertNumberOfCalls(t, "Delete", 1)
	old2.AssertNumberOfCalls(t, "Delete", 1)
	old3.AssertNumberOfCalls(t, "Delete", 1)
}

// Make a MockMessage of a specific age
func mockMessage(ageHours int) *MockMessage {
	msg := &MockMessage{}
	msg.On("ID").Return(fmt.Sprintf("MSG[age=%vh]", ageHours))
	msg.On("Date").Return(time.Now().Add(time.Duration(ageHours*-1) * time.Hour))
	msg.On("Delete").Return(nil)
	return msg
}

// Mock DataStore object
type MockDataStore struct {
	mock.Mock
}

func (m *MockDataStore) MailboxFor(name string) (Mailbox, error) {
	args := m.Called()
	return args.Get(0).(Mailbox), args.Error(1)
}

func (m *MockDataStore) AllMailboxes() ([]Mailbox, error) {
	args := m.Called()
	return args.Get(0).([]Mailbox), args.Error(1)
}

// Mock Mailbox object
type MockMailbox struct {
	mock.Mock
}

func (m *MockMailbox) GetMessages() ([]Message, error) {
	args := m.Called()
	return args.Get(0).([]Message), args.Error(1)
}

func (m *MockMailbox) GetMessage(id string) (Message, error) {
	args := m.Called(id)
	return args.Get(0).(Message), args.Error(1)
}

func (m *MockMailbox) Purge() error {
	args := m.Called()
	return args.Error(0)
}

func (m *MockMailbox) NewMessage() (Message, error) {
	args := m.Called()
	return args.Get(0).(Message), args.Error(1)
}

func (m *MockMailbox) String() string {
	args := m.Called()
	return args.String(0)
}

// Mock Message object
type MockMessage struct {
	mock.Mock
}

func (m *MockMessage) ID() string {
	args := m.Called()
	return args.String(0)
}

func (m *MockMessage) From() string {
	args := m.Called()
	return args.String(0)
}

func (m *MockMessage) Date() time.Time {
	args := m.Called()
	return args.Get(0).(time.Time)
}

func (m *MockMessage) Subject() string {
	args := m.Called()
	return args.String(0)
}

func (m *MockMessage) ReadHeader() (msg *mail.Message, err error) {
	args := m.Called()
	return args.Get(0).(*mail.Message), args.Error(1)
}

func (m *MockMessage) ReadBody() (body *enmime.MIMEBody, err error) {
	args := m.Called()
	return args.Get(0).(*enmime.MIMEBody), args.Error(1)
}

func (m *MockMessage) ReadRaw() (raw *string, err error) {
	args := m.Called()
	return args.Get(0).(*string), args.Error(1)
}

func (m *MockMessage) RawReader() (reader io.ReadCloser, err error) {
	args := m.Called()
	return args.Get(0).(io.ReadCloser), args.Error(1)
}

func (m *MockMessage) Size() int64 {
	args := m.Called()
	return int64(args.Int(0))
}

func (m *MockMessage) Append(data []byte) error {
	// []byte arg seems to mess up testify/mock
	return nil
}

func (m *MockMessage) Close() error {
	args := m.Called()
	return args.Error(0)
}

func (m *MockMessage) Delete() error {
	args := m.Called()
	return args.Error(0)
}

func (m *MockMessage) String() string {
	args := m.Called()
	return args.String(0)
}
