// Copyright (c) 2020 Proton Technologies AG
//
// This file is part of ProtonMail 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 imap

import (
	"io"
	"net/mail"

	pmcrypto "github.com/ProtonMail/gopenpgp/crypto"
	"github.com/ProtonMail/proton-bridge/internal/imap/uidplus"
	"github.com/ProtonMail/proton-bridge/internal/store"
	"github.com/ProtonMail/proton-bridge/pkg/pmapi"
)

type storeUserProvider interface {
	UserID() string
	GetSpace() (usedSpace, maxSpace uint, err error)
	GetMaxUpload() (uint, error)

	GetAddress(addressID string) (storeAddressProvider, error)

	CreateDraft(
		kr *pmcrypto.KeyRing,
		message *pmapi.Message,
		attachmentReaders []io.Reader,
		attachedPublicKey,
		attachedPublicKeyName string,
		parentID string) (*pmapi.Message, []*pmapi.Attachment, error)
}

type storeAddressProvider interface {
	AddressString() string
	AddressID() string
	APIAddress() *pmapi.Address

	CreateMailbox(name string) error
	ListMailboxes() []storeMailboxProvider
	GetMailbox(name string) (storeMailboxProvider, error)
}

type storeMailboxProvider interface {
	LabelID() string
	Name() string
	Color() string
	IsSystem() bool
	IsFolder() bool
	UIDValidity() uint32

	Rename(newName string) error
	Delete() error

	GetAPIIDsFromUIDRange(start, stop uint32) ([]string, error)
	GetAPIIDsFromSequenceRange(start, stop uint32) ([]string, error)
	GetLatestAPIID() (string, error)
	GetNextUID() (uint32, error)
	GetCounts() (dbTotal, dbUnread uint, err error)
	GetUIDList(apiIDs []string) *uidplus.OrderedSeq
	GetUIDByHeader(header *mail.Header) uint32
	GetDelimiter() string

	GetMessage(apiID string) (storeMessageProvider, error)
	FetchMessage(apiID string) (storeMessageProvider, error)
	LabelMessages(apiID []string) error
	UnlabelMessages(apiID []string) error
	MarkMessagesRead(apiID []string) error
	MarkMessagesUnread(apiID []string) error
	MarkMessagesStarred(apiID []string) error
	MarkMessagesUnstarred(apiID []string) error
	ImportMessage(msg *pmapi.Message, body []byte, labelIDs []string) error
	DeleteMessages(apiID []string) error
}

type storeMessageProvider interface {
	ID() string
	UID() (uint32, error)
	SequenceNumber() (uint32, error)
	Message() *pmapi.Message

	SetSize(int64) error
	SetContentTypeAndHeader(string, mail.Header) error
}

type storeUserWrap struct {
	*store.Store
}

// newStoreUserWrap wraps store struct into local storeUserWrap to implement local
// interface. The problem is that store returns the store package's Address type, so
// every method that returns an address has to be overridden to fulfill the interface.
// The same is true for other store structs i.e. storeAddress or storeMailbox.
func newStoreUserWrap(store *store.Store) *storeUserWrap {
	return &storeUserWrap{Store: store}
}

func (s *storeUserWrap) GetAddress(addressID string) (storeAddressProvider, error) {
	address, err := s.Store.GetAddress(addressID)
	if err != nil {
		return nil, err
	}
	return newStoreAddressWrap(address), nil
}

type storeAddressWrap struct {
	*store.Address
}

func newStoreAddressWrap(address *store.Address) *storeAddressWrap {
	return &storeAddressWrap{Address: address}
}

func (s *storeAddressWrap) ListMailboxes() []storeMailboxProvider {
	mailboxes := []storeMailboxProvider{}
	for _, mailbox := range s.Address.ListMailboxes() {
		mailboxes = append(mailboxes, newStoreMailboxWrap(mailbox))
	}
	return mailboxes
}

func (s *storeAddressWrap) GetMailbox(name string) (storeMailboxProvider, error) {
	mailbox, err := s.Address.GetMailbox(name)
	if err != nil {
		return nil, err
	}
	return newStoreMailboxWrap(mailbox), nil
}

type storeMailboxWrap struct {
	*store.Mailbox
}

func newStoreMailboxWrap(mailbox *store.Mailbox) *storeMailboxWrap {
	return &storeMailboxWrap{Mailbox: mailbox}
}

func (s *storeMailboxWrap) GetMessage(apiID string) (storeMessageProvider, error) {
	return s.Mailbox.GetMessage(apiID)
}

func (s *storeMailboxWrap) FetchMessage(apiID string) (storeMessageProvider, error) {
	return s.Mailbox.FetchMessage(apiID)
}
