// Copyright (c) 2020 Proton Technologies AG
//
// This file is part of ProtonMail Bridge.Bridge.
//
// ProtonMail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ProtonMail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with ProtonMail Bridge.  If not, see <https://www.gnu.org/licenses/>.

package tests

import (
	"fmt"
	"net/mail"
	"strconv"
	"strings"

	"github.com/ProtonMail/proton-bridge/pkg/pmapi"
	"github.com/cucumber/godog"
	"github.com/cucumber/godog/gherkin"
)

func StoreSetupFeatureContext(s *godog.Suite) {
	s.Step(`^there is "([^"]*)" with mailboxes`, thereIsUserWithMailboxes)
	s.Step(`^there is "([^"]*)" with mailbox "([^"]*)"$`, thereIsUserWithMailbox)
	s.Step(`^there are messages in mailbox(?:es)? "([^"]*)" for "([^"]*)"$`, thereAreMessagesInMailboxesForUser)
	s.Step(`^there are messages in mailbox(?:es)? "([^"]*)" for address "([^"]*)" of "([^"]*)"$`, thereAreMessagesInMailboxesForAddressOfUser)
	s.Step(`^there are (\d+) messages in mailbox(?:es)? "([^"]*)" for "([^"]*)"$`, thereAreSomeMessagesInMailboxesForUser)
	s.Step(`^there are messages for "([^"]*)" as follows$`, thereAreSomeMessagesForUserAsFollows)
	s.Step(`^there are (\d+) messages in mailbox(?:es)? "([^"]*)" for address "([^"]*)" of "([^"]*)"$`, thereAreSomeMessagesInMailboxesForAddressOfUser)
}

func thereIsUserWithMailboxes(bddUserID string, mailboxes *gherkin.DataTable) error {
	for _, row := range mailboxes.Rows {
		if err := thereIsUserWithMailbox(bddUserID, row.Cells[0].Value); err != nil {
			return err
		}
	}
	return nil
}

func thereIsUserWithMailbox(bddUserID, mailboxName string) error {
	account := ctx.GetTestAccount(bddUserID)
	if account == nil {
		return godog.ErrPending
	}
	err := ctx.GetPMAPIController().AddUserLabel(account.Username(), &pmapi.Label{
		Name: mailboxName,
		Type: pmapi.LabelTypeMailbox,
	})
	if err != nil {
		return internalError(err, "adding label %s for %s", mailboxName, account.Username())
	}
	store, err := ctx.GetStore(account.Username())
	if err != nil {
		return internalError(err, "getting store of %s", account.Username())
	}
	return internalError(store.RebuildMailboxes(), "rebuilding mailboxes")
}

func thereAreMessagesInMailboxesForUser(mailboxNames, bddUserID string, messages *gherkin.DataTable) error {
	return thereAreMessagesInMailboxesForAddressOfUser(mailboxNames, "", bddUserID, messages)
}

func thereAreMessagesInMailboxesForAddressOfUser(mailboxNames, bddAddressID, bddUserID string, messages *gherkin.DataTable) error {
	account := ctx.GetTestAccountWithAddress(bddUserID, bddAddressID)
	if account == nil {
		return godog.ErrPending
	}
	head := messages.Rows[0].Cells
	for i := 1; i < len(messages.Rows); i++ {
		labelIDs, err := ctx.GetPMAPIController().GetLabelIDs(account.Username(), strings.Split(mailboxNames, ","))
		if err != nil {
			return internalError(err, "getting labels %s for %s", mailboxNames, account.Username())
		}
		message := &pmapi.Message{
			MIMEType:  "text/plain",
			LabelIDs:  labelIDs,
			AddressID: account.AddressID(),
		}
		for n, cell := range messages.Rows[i].Cells {
			switch head[n].Value {
			case "from":
				message.Sender = &mail.Address{
					Address: ctx.EnsureAddress(account.Username(), cell.Value),
				}
			case "to":
				message.AddressID = ctx.EnsureAddressID(account.Username(), cell.Value)
				message.ToList = []*mail.Address{{
					Address: ctx.EnsureAddress(account.Username(), cell.Value),
				}}
			case "subject":
				message.Subject = cell.Value
			case "body":
				message.Body = cell.Value
			case "read":
				unread := 1
				if cell.Value == "true" {
					unread = 0
				}
				message.Unread = unread
			case "starred":
				if cell.Value == "true" {
					message.LabelIDs = append(message.LabelIDs, "10")
				}
			default:
				return fmt.Errorf("unexpected column name: %s", head[n].Value)
			}
		}
		if err := ctx.GetPMAPIController().AddUserMessage(account.Username(), message); err != nil {
			return internalError(err, "adding message")
		}
	}
	return internalError(ctx.WaitForSync(account.Username()), "waiting for sync")
}

func thereAreSomeMessagesInMailboxesForUser(numberOfMessages int, mailboxNames, bddUserID string) error {
	return thereAreSomeMessagesInMailboxesForAddressOfUser(numberOfMessages, mailboxNames, "", bddUserID)
}

func thereAreSomeMessagesForUserAsFollows(bddUserID string, structure *gherkin.DataTable) error {
	return processMailboxStructureDataTable(structure, func(bddAddressID string, mailboxNames string, numberOfMessages int) error {
		return thereAreSomeMessagesInMailboxesForAddressOfUser(numberOfMessages, mailboxNames, bddAddressID, bddUserID)
	})
}

func processMailboxStructureDataTable(structure *gherkin.DataTable, callback func(string, string, int) error) error {
	head := structure.Rows[0].Cells
	for i := 1; i < len(structure.Rows); i++ {
		bddAddressID := ""
		mailboxNames := "INBOX"
		numberOfMessages := 0
		for n, cell := range structure.Rows[i].Cells {
			switch head[n].Value {
			case "address":
				bddAddressID = cell.Value
			case "mailboxes":
				mailboxNames = cell.Value
			case "messages":
				number, err := strconv.Atoi(cell.Value)
				if err != nil {
					return internalError(err, "converting number of messages to integer")
				}
				numberOfMessages = number
			default:
				return fmt.Errorf("unexpected column name: %s", head[n].Value)
			}
		}

		if err := callback(bddAddressID, mailboxNames, numberOfMessages); err != nil {
			return err
		}
	}
	return nil
}

func thereAreSomeMessagesInMailboxesForAddressOfUser(numberOfMessages int, mailboxNames, bddAddressID, bddUserID string) error {
	account := ctx.GetTestAccountWithAddress(bddUserID, bddAddressID)
	if account == nil {
		return godog.ErrPending
	}

	for i := 1; i <= numberOfMessages; i++ {
		labelIDs, err := ctx.GetPMAPIController().GetLabelIDs(account.Username(), strings.Split(mailboxNames, ","))
		if err != nil {
			return internalError(err, "getting labels %s for %s", mailboxNames, account.Username())
		}
		err = ctx.GetPMAPIController().AddUserMessage(account.Username(), &pmapi.Message{
			MIMEType:  "text/plain",
			LabelIDs:  labelIDs,
			AddressID: account.AddressID(),
			Subject:   fmt.Sprintf("Test message #%d", i),
			Sender:    &mail.Address{Address: "anyone@example.com"},
			ToList:    []*mail.Address{{Address: account.Address()}},
		})
		if err != nil {
			return internalError(err, "adding message")
		}
	}
	return internalError(ctx.WaitForSync(account.Username()), "waiting for sync")
}
