// Copyright 2023 schukai GmbH
// SPDX-License-Identifier: AGPL-3.0

package jobqueue

import (
	"context"
	"fmt"
	"net/smtp"
	"sync"
)

func NewMailRunnableFromMap(data map[string]interface{}) (*MailRunnable, error) {
	to, ok := data["to"].(string)
	if !ok {
		return nil, fmt.Errorf("%w: Invalid To: %v", ErrInvalidData, data["to"])
	}

	from, ok := data["from"].(string)
	if !ok {
		return nil, fmt.Errorf("%w: Invalid From: %v", ErrInvalidData, data["from"])
	}

	subject, ok := data["subject"].(string)
	if !ok {
		return nil, fmt.Errorf("%w: Invalid Subject: %v", ErrInvalidData, data["subject"])
	}

	body, ok := data["l"].(string)
	if !ok {
		return nil, fmt.Errorf("%w: Invalid Body: %v", ErrInvalidData, data["body"])
	}

	server, ok := data["server"].(string)
	if !ok {
		return nil, fmt.Errorf("%w: Invalid Server: %v", ErrInvalidData, data["server"])
	}

	port, ok := data["port"].(string)
	if !ok {
		return nil, fmt.Errorf("%w: Invalid Port: %v", ErrInvalidData, data["port"])
	}

	username, ok := data["username"].(string)
	if !ok {
		return nil, fmt.Errorf("%w: Invalid Username: %v", ErrInvalidData, data["username"])
	}

	password, ok := data["password"].(string)
	if !ok {
		return nil, fmt.Errorf("%w: Invalid Password: %v", ErrInvalidData, data["password"])
	}

	headers, ok := data["headers"].(map[string]string)
	if !ok {
		return nil, fmt.Errorf("%w: Invalid Headers: %v", ErrInvalidData, data["headers"])
	}

	return &MailRunnable{
		To:       to,
		From:     from,
		Subject:  subject,
		Body:     body,
		Server:   server,
		Port:     port,
		Username: username,
		Password: password,
		Headers:  headers,
	}, nil
}

// MailResult is a result of a email
type MailResult struct {
	Sent           bool
	ServerReply    string
	SmtpStatusCode uint
}

func (m *MailResult) GetResult() string {
	return fmt.Sprintf("Sent: %t\n\nServerReply: %s\n\nSmtpStatusCode: %d", m.Sent, m.ServerReply, m.SmtpStatusCode)
}

func (m *MailResult) GetError() (string, int) {
	if !m.Sent {
		return fmt.Sprintf("Sent: %t\n\nServerReply: %s\n\nSmtpStatusCode: %d", m.Sent, m.ServerReply, m.SmtpStatusCode), 1
	}
	return "", 0
}

type MailRunnable struct {
	To       string
	From     string
	Subject  string
	Body     string
	Server   string
	Port     string
	Username string
	Password string
	Headers  map[string]string
	mu       sync.Mutex
}

func (m *MailRunnable) Run(_ context.Context) (RunResult[MailResult], error) {

	smtpServer := m.Server + ":" + m.Port

	// Connect to the remote SMTP server.
	client, err := smtp.Dial(smtpServer)
	if err != nil {
		return RunResult[MailResult]{Status: ResultStatusFailed}, err
	}

	if client != nil {
		defer client.Close()
	}

	if m.Username != "" && m.Password != "" {
		if err := client.Auth(smtp.PlainAuth("", m.Username, m.Password, m.Server)); err != nil {
			return RunResult[MailResult]{Status: ResultStatusFailed}, err
		}
	}

	// To && From.
	if err := client.Mail(m.From); err != nil {
		return RunResult[MailResult]{Status: ResultStatusFailed}, err
	}
	if err := client.Rcpt(m.To); err != nil {
		return RunResult[MailResult]{Status: ResultStatusFailed}, err
	}

	// Headers and Data.
	writer, err := client.Data()
	if err != nil {
		return RunResult[MailResult]{Status: ResultStatusFailed}, err
	}

	headers := "From: " + m.From + "\r\n"
	headers += "To: " + m.To + "\r\n"
	headers += "Subject: " + m.Subject + "\r\n"

	for key, value := range m.Headers {
		headers += key + ": " + value + "\r\n"
	}

	_, err = writer.Write([]byte(headers + "\r\n" + m.Body))
	if err != nil {
		return RunResult[MailResult]{Status: ResultStatusFailed}, err
	}

	_ = writer.Close()

	// Quit and get the SMTP status code.
	smtpStatusCode, _ := client.Text.Cmd("QUIT")

	return RunResult[MailResult]{Status: ResultStatusSuccess, Data: MailResult{Sent: true, SmtpStatusCode: smtpStatusCode}}, nil
}

func (m *MailRunnable) GetType() string {
	return "mail"
}

func (m *MailRunnable) GetPersistence() RunnableImport {
	m.mu.Lock()
	defer m.mu.Unlock()

	data := JSONMap{
		"to":       m.To,
		"from":     m.From,
		"subject":  m.Subject,
		"body":     m.Body,
		"server":   m.Server,
		"port":     m.Port,
		"username": m.Username,
		"password": m.Password,
		"headers":  m.Headers,
	}

	return RunnableImport{
		Type: m.GetType(),
		Data: data,
	}
}