package smtp

import (
	"bytes"
	"io"
	"net/mail"
	"time"

	smtplib "github.com/emersion/go-smtp"
	"go.uber.org/zap"
)

type backend struct {
	honeypot *SMTPHoneypot
}

func (b *backend) Login(state *smtplib.ConnectionState, username, password string) (smtplib.Session, error) {
	sess := &session{
		honeypot:      b.honeypot,
		loginUsername: username,
		loginPassword: password,
		remoteAddr:    state.RemoteAddr.String(),
	}
	return sess, nil
}
func (b *backend) AnonymousLogin(state *smtplib.ConnectionState) (smtplib.Session, error) {
	sess := &session{
		loginAnonymous: true,
		remoteAddr:     state.RemoteAddr.String(),
	}
	return sess, nil
}

type session struct {
	honeypot *SMTPHoneypot

	loginUsername  string
	loginPassword  string
	loginAnonymous bool
	remoteAddr     string

	mail MailData
}

func (sess *session) Reset() {
}
func (sess *session) Logout() error {
	return nil
}
func (sess *session) Mail(from string, opts smtplib.MailOptions) error {
	sess.mail.From = from
	return nil
}
func (sess *session) Rcpt(to string) error {
	sess.mail.Recipients = append(sess.mail.Recipients, to)
	return nil
}
func (sess *session) Data(r io.Reader) error {
	lr := io.LimitReader(r, 1024*1024)
	var buf bytes.Buffer
	n, err := io.Copy(&buf, lr)
	if err != nil {
		sess.honeypot.Logger.Debugw("Error reading email data from client.", "error", err, "remote_addr", sess.remoteAddr)
	}

	if n == 1024*1024 {
		// There is probably more data, read the rest from the client
		// We don't really care what happens to this, or if it errors
		_, _ = io.Copy(io.Discard, r)
	}

	sess.mail.Data = buf.String()

	var toAddr string
	var subject string
	// Parse mail headers
	msg, err := mail.ReadMessage(&buf)
	if err != nil {
		sess.honeypot.Logger.Infow("Unable to parse mail.", "error", err, "remote_addr", sess.remoteAddr)
	} else {
		toAddr = msg.Header.Get("To")
		subject = msg.Header.Get("Subject")
	}

	if err := sess.honeypot.Store.AddMailData(&sess.mail); err != nil {
		sess.honeypot.Logger.Warnw("Error storing maildata.", "error", err, "remote_addr", sess.remoteAddr)
		return smtplib.ErrDataTooLarge
	}
	sess.honeypot.Logger.Infow("Mail received", "from", sess.mail.From, "to", toAddr, "subject", subject, "remote_addr", sess.remoteAddr, "size", len(sess.mail.Data))

	return nil
}

type SMTPHoneypot struct {
	Addr   string
	Logger *zap.SugaredLogger
	Store  SMTPStore
	server *smtplib.Server
}

func NewSMTPHoneypot() (*SMTPHoneypot, error) {
	honeypot := &SMTPHoneypot{
		Logger: zap.NewNop().Sugar(),
	}

	return honeypot, nil
}

func (sh *SMTPHoneypot) ListenAndServe() error {
	server := smtplib.NewServer(&backend{
		honeypot: sh,
	})
	server.Addr = sh.Addr
	server.Domain = "apiary.t-juice.club"
	server.AllowInsecureAuth = true
	server.ReadTimeout = 10 * time.Second
	server.WriteTimeout = 10 * time.Second
	sh.server = server
	return server.ListenAndServe()
}

func (sh *SMTPHoneypot) Shutdown() error {
	return sh.server.Close()
}
