package mail

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"log/slog"

	"github.com/Queueue0/jums/internal/config"
	"github.com/Queueue0/jums/internal/smtp/packets"
)

type Mail struct {
	From     *Address
	Rcpt     []Address
	Data     []byte
	Id       string
	Received PartialReceived
}

func (m *Mail) Send() error {
	fmt.Println("Sending mail...")
	var ce *CompiledErrors = nil
	appendErr := func(err error) {
		if ce == nil {
			ce = &CompiledErrors{}
		}
		ce.Append(err)
	}

	g := m.groupRcpts()
	config := config.GetConfig()
	for domain, addrs := range g {
		if domain == config.Domain {
			for _, addr := range addrs {
				m.Deliver(addr)
			}
			continue
		}

		c, err := createSMTPConn(domain)
		if err != nil {
			appendErr(err)
			continue
		}

		for _, addr := range addrs {
			fmt.Println(m.Received.format(addr))
			data := append([]byte(m.Received.format(addr)), m.Data...)
			// TODO: handle these errors
			_ = packets.NewCommand("MAIL", fmt.Sprintf("FROM:<%s>", m.From.String())).Send(c)
			s, _ := readAndParseStatus(c)
			if s.Code() != 250 {
				_ = packets.NewCommand("RSET").Send(c)
				readAndParseStatus(c)
				continue
			}

			_ = packets.NewCommand("RCPT", fmt.Sprintf("TO:<%s>", addr.String())).Send(c)
			s, _ = readAndParseStatus(c)
			if s.Code() != 250 {
				_ = packets.NewCommand("RSET").Send(c)
				readAndParseStatus(c)
				continue
			}

			_ = packets.NewCommand("DATA").Send(c)
			s, _ = readAndParseStatus(c)
			// accept 250 even though it's not strictly in spec
			if s.Code() != 354 && s.Code() != 250 {
				_ = packets.NewCommand("RSET").Send(c)
				readAndParseStatus(c)
				continue
			}

			c.Write(data)
			readAndParseStatus(c)
		}
		_ = packets.NewCommand("QUIT").Send(c)
		readAndParseStatus(c)
		c.Close()
	}

	return ce
}

// Deliver mail addressed to users at our domain
func (m *Mail) Deliver(addr Address) error {
	slog.Info("Deliering mail", "to", addr.String())
	return nil
}

func (m *Mail) GenerateId() error {
	h := sha256.New()
	_, err := h.Write(m.Data)
	if err != nil {
		return err
	}

	m.Id = hex.EncodeToString(h.Sum(nil))
	return nil
}

func (m *Mail) groupRcpts() map[string][]Address {
	g := make(map[string][]Address)
	for _, r := range m.Rcpt {
		if _, ok := g[r.Domain]; !ok {
			g[r.Domain] = []Address{}
		}
		g[r.Domain] = append(g[r.Domain], r)
	}

	return g
}

type PartialReceived struct {
	From      string
	By        string
	With      string
	TlsInfo   string
	Id        string
	Timestamp string
}

func (r *PartialReceived) format(forAddr Address) string {
	return fmt.Sprintf("Received: %s\r\n\t%s %s\r\n\t%s\r\n\t%s\r\n\tfor %s;\r\n\t%s\r\n", r.From, r.By, r.With, r.TlsInfo, r.Id, forAddr.SmtpFormat(), r.Timestamp)
}