package email

import (
	"bytes"
	"crypto/tls"
	"encoding/base64"
	"fmt"
	"io"
	"net/smtp"
	"strings"
)

const (
	mime                   = "MIME-version: 1.0;\nContent-Type: text/html; charset=\"UTF-8\";\n\n"
	subjectReport          = "B2 vs Local Files"
	templateSuccess        = "success.html"
	templateConfirm        = "confirm.html"
	templateForgotPassword = "forgot_password.html"
	templateRegister       = "register.html"
	delimeter              = "**=myohmy689407924327"
)

type EmailService struct {
	auth      smtp.Auth
	host      string
	port      string
	from      string
	tlsconfig *tls.Config
}

type EmailWithAttachments struct {
	To          string
	Bucket      string
	BackupDir   string
	Count       map[string]int
	Attachments []EmailAttachment
}

type EmailAttachment struct {
	File  io.Reader
	Title string
}

func (e EmailAttachment) ReadContent() ([]byte, error) {
	bts, err := io.ReadAll(e.File)
	if err != nil {
		return nil, fmt.Errorf("error loading attachment: %s", err)
	}
	return bts, nil
}

type MailServiceConfig struct {
	Auth smtp.Auth
	Host string
	Port string
	From string // Sender email address
}

func NewMailService(config MailServiceConfig) *EmailService {
	return &EmailService{
		auth: config.Auth,
		host: config.Host,
		port: config.Port,
		from: config.From,
		tlsconfig: &tls.Config{
			InsecureSkipVerify: true,
			ServerName:         config.Host,
		},
	}
}

func (e *EmailService) SendOK(emailData EmailWithAttachments) error {
	template := strings.Replace(htmlTemplate, "{{bucket}}", emailData.Bucket, -1)
	template = strings.Replace(template, "{{local_backup_path}}", emailData.BackupDir, -1)
	for k, v := range emailData.Count {
		template = strings.Replace(template, "{{"+k+"}}", fmt.Sprint(v), -1)
	}
	msg, err := newMessage(e.from, emailData.To, subjectReport).
		withAttachments(template, emailData.Attachments)

	if err != nil {
		return fmt.Errorf("error while upload attachments %w", err)
	}

	return e.send(emailData.To, msg)
}

func (e *EmailService) send(to string, msg []byte) error {
	c, err := smtp.Dial(e.host + ":" + e.port)
	if err != nil {
		return fmt.Errorf("DIAL: %s", err)
	}

	if err = c.StartTLS(e.tlsconfig); err != nil {
		return fmt.Errorf("c.StartTLS: %s", err)
	}

	// Auth
	if err = c.Auth(e.auth); err != nil {
		return fmt.Errorf("c.Auth: %s", err)
	}

	// To && From
	if err = c.Mail(e.from); err != nil {
		return fmt.Errorf("c.Mail: %s", err)
	}

	if err = c.Rcpt(to); err != nil {
		return fmt.Errorf("c.Rcpt: %s", err)
	}

	// Data
	w, err := c.Data()
	if err != nil {
		return fmt.Errorf("c.Data: %s", err)
	}

	written, err := w.Write(msg)
	if err != nil {
		return fmt.Errorf("w.Write: %s", err)
	}

	if written <= 0 {
		return fmt.Errorf("%d bytes written", written)
	}

	if err = w.Close(); err != nil {
		return fmt.Errorf("w.Close: %s", err)
	}

	if err = c.Quit(); err != nil {
		return fmt.Errorf("w.Quit: %s", err)
	}
	return nil
}

type message struct {
	from    string
	to      string
	subject string
}

func newMessage(from, to, subject string) message {
	return message{from: from, to: to, subject: subject}
}

func (m message) withAttachments(body string, attachments []EmailAttachment) ([]byte, error) {
	headers := make(map[string]string)
	headers["From"] = m.from
	headers["To"] = m.to
	headers["Subject"] = m.subject
	headers["MIME-Version"] = "1.0"

	var message bytes.Buffer

	for k, v := range headers {
		message.WriteString(k)
		message.WriteString(": ")
		message.WriteString(v)
		message.WriteString("\r\n")
	}

	message.WriteString("Content-Type: " + fmt.Sprintf("multipart/mixed; boundary=\"%s\"\r\n", delimeter))
	message.WriteString("--" + delimeter + "\r\n")
	message.WriteString("Content-Type: text/html; charset=\"UTF-8\"\r\n\r\n")
	message.WriteString(body + "\r\n\r\n")

	for _, attachment := range attachments {
		attachmentRawFile, err := attachment.ReadContent()
		if err != nil {
			return nil, err
		}
		message.WriteString("--" + delimeter + "\r\n")
		message.WriteString("Content-Disposition: attachment; filename=\"" + attachment.Title + "\"\r\n")
		message.WriteString("Content-Type: application/octet-stream\r\n")
		message.WriteString("Content-Transfer-Encoding: base64\r\n\r\n")
		message.WriteString(base64.StdEncoding.EncodeToString(attachmentRawFile) + "\r\n")
	}

	message.WriteString("--" + delimeter + "--") // End the message
	return message.Bytes(), nil
}