// Package channels provides different communication channels to notify users.
package channels

import (
	"context"
	"crypto/tls"
	"fmt"
	"strings"

	"github.com/owncloud/ocis/v2/ocis-pkg/log"
	"github.com/owncloud/ocis/v2/services/notifications/pkg/config"
	"github.com/pkg/errors"
	mail "github.com/xhit/go-simple-mail/v2"
)

// Channel defines the methods of a communication channel.
type Channel interface {
	// SendMessage sends a message to users.
	SendMessage(ctx context.Context, message *Message) error
}

// Message represent the already rendered message including the user id opaqueID
type Message struct {
	Sender       string
	Recipient    []string
	Subject      string
	TextBody     string
	HTMLBody     string
	AttachInline map[string][]byte
}

// NewMailChannel instantiates a new mail communication channel.
func NewMailChannel(cfg config.Config, logger log.Logger) (Channel, error) {
	return Mail{
		conf:   cfg,
		logger: logger,
	}, nil
}

// Mail is the communication channel for email.
type Mail struct {
	conf   config.Config
	logger log.Logger
}

func (m Mail) getMailClient() (*mail.SMTPClient, error) {
	server := mail.NewSMTPClient()
	server.Host = m.conf.Notifications.SMTP.Host
	server.Port = m.conf.Notifications.SMTP.Port
	server.Username = m.conf.Notifications.SMTP.Username
	if server.Username == "" {
		// compatibility fallback
		server.Username = m.conf.Notifications.SMTP.Sender
	}
	server.Password = m.conf.Notifications.SMTP.Password
	if server.TLSConfig == nil {
		server.TLSConfig = &tls.Config{
			MinVersion: tls.VersionTLS12,
		}
	}
	server.TLSConfig.InsecureSkipVerify = m.conf.Notifications.SMTP.Insecure

	switch strings.ToLower(m.conf.Notifications.SMTP.Authentication) {
	case "login":
		server.Authentication = mail.AuthLogin
	case "plain":
		server.Authentication = mail.AuthPlain
	case "crammd5":
		server.Authentication = mail.AuthCRAMMD5
	case "none":
		server.Authentication = mail.AuthNone
	case "auto", "":
		server.Authentication = mail.AuthAuto
	default:
		return nil, errors.New("unknown mail authentication method")
	}

	switch strings.ToLower(m.conf.Notifications.SMTP.Encryption) {
	case "tls":
		server.Encryption = mail.EncryptionTLS
		server.TLSConfig.ServerName = m.conf.Notifications.SMTP.Host
	case "starttls":
		server.Encryption = mail.EncryptionSTARTTLS
		server.TLSConfig.ServerName = m.conf.Notifications.SMTP.Host
	case "ssl":
		server.Encryption = mail.EncryptionSSL
	case "ssltls":
		server.Encryption = mail.EncryptionSSLTLS
	case "none":
		server.Encryption = mail.EncryptionNone
	default:
		return nil, errors.New("unknown mail encryption method")
	}

	smtpClient, err := server.Connect()
	if err != nil {
		return nil, err
	}

	return smtpClient, nil
}

// SendMessage sends a message to all given users.
func (m Mail) SendMessage(ctx context.Context, message *Message) error {
	if m.conf.Notifications.SMTP.Host == "" {
		m.logger.Info().Str("mail", "SendMessage").Msg("failed to send a message. SMTP host is  not set")
		return nil
	}

	smtpClient, err := m.getMailClient()
	if err != nil {
		return err
	}

	email := mail.NewMSG()
	if message.Sender != "" {
		email.SetFrom(fmt.Sprintf("%s via %s", message.Sender, m.conf.Notifications.SMTP.Sender)).AddTo(message.Recipient...)
	} else {
		email.SetFrom(m.conf.Notifications.SMTP.Sender).AddTo(message.Recipient...)
	}
	email.SetSubject(message.Subject)
	email.SetBody(mail.TextPlain, message.TextBody)
	if message.HTMLBody != "" {
		email.AddAlternative(mail.TextHTML, message.HTMLBody)
		for filename, data := range message.AttachInline {
			email.Attach(&mail.File{Data: data, Name: filename, Inline: true})
		}
	}

	return email.Send(smtpClient)
}
