package notification

import (
	"context"
	"net"
	"strings"
	"sync"
	"testing"
	"time"
)

// fakeSMTPServer accepts a single connection, runs the minimal SMTP
// dialogue (HELO/EHLO, MAIL FROM, RCPT TO, DATA, QUIT) and stores
// what came across the wire. Plain (no TLS) — we test the protocol
// shape, not crypto.
type fakeSMTPServer struct {
	mu       sync.Mutex
	mailFrom string
	rcptTo   string
	data     string
	authed   bool
}

func startFakeSMTP(t *testing.T) (string, *fakeSMTPServer) {
	t.Helper()
	ln, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		t.Fatalf("listen: %v", err)
	}
	srv := &fakeSMTPServer{}
	t.Cleanup(func() { _ = ln.Close() })
	go func() {
		conn, err := ln.Accept()
		if err != nil {
			return
		}
		defer func() { _ = conn.Close() }()
		readLine := func() string {
			buf := make([]byte, 1024)
			n, err := conn.Read(buf)
			if err != nil {
				return ""
			}
			return string(buf[:n])
		}
		write := func(s string) { _, _ = conn.Write([]byte(s)) }

		write("220 fake.smtp ESMTP\r\n")
		for {
			line := readLine()
			if line == "" {
				return
			}
			cmd := strings.ToUpper(strings.TrimSpace(line))
			switch {
			case strings.HasPrefix(cmd, "EHLO"), strings.HasPrefix(cmd, "HELO"):
				write("250-fake.smtp\r\n250 AUTH PLAIN\r\n")
			case strings.HasPrefix(cmd, "AUTH "):
				srv.mu.Lock()
				srv.authed = true
				srv.mu.Unlock()
				write("235 OK\r\n")
			case strings.HasPrefix(cmd, "MAIL FROM"):
				srv.mu.Lock()
				srv.mailFrom = strings.TrimSpace(strings.TrimPrefix(line, "MAIL FROM:"))
				srv.mu.Unlock()
				write("250 OK\r\n")
			case strings.HasPrefix(cmd, "RCPT TO"):
				srv.mu.Lock()
				srv.rcptTo = strings.TrimSpace(strings.TrimPrefix(line, "RCPT TO:"))
				srv.mu.Unlock()
				write("250 OK\r\n")
			case cmd == "DATA":
				write("354 OK\r\n")
				// read until "\r\n.\r\n"
				var data strings.Builder
				for {
					chunk := readLine()
					if chunk == "" {
						break
					}
					data.WriteString(chunk)
					if strings.Contains(data.String(), "\r\n.\r\n") {
						break
					}
				}
				srv.mu.Lock()
				srv.data = data.String()
				srv.mu.Unlock()
				write("250 OK\r\n")
			case cmd == "QUIT":
				write("221 bye\r\n")
				return
			default:
				write("500 unknown\r\n")
			}
		}
	}()
	return ln.Addr().String(), srv
}

func TestSMTPSendsExpectedHeaders(t *testing.T) {
	t.Parallel()
	addr, srv := startFakeSMTP(t)
	host, port := splitHostPort(addr)

	ch := NewSMTPChannel(SMTPConfig{
		Host: host, Port: port, Encryption: "none",
		Username: "u", Password: "p",
		From: "Restic-Manager <alerts@example.com>",
		To:   "ops@example.com",
	}, "rm.example")

	_, _, err := ch.Send(context.Background(), Payload{
		Event: EventRaised, AlertID: "01ABC",
		Severity: "warning", Kind: "backup_failed",
		HostName: "alfa-01", Message: "Backup failed: 401",
		RaisedAt: time.Date(2026, 5, 4, 15, 42, 1, 0, time.UTC),
		Link:     "https://rm.example/alerts/01ABC",
	})
	if err != nil {
		t.Fatalf("send: %v", err)
	}

	srv.mu.Lock()
	defer srv.mu.Unlock()
	if !srv.authed {
		t.Errorf("AUTH never sent")
	}
	if !strings.Contains(srv.mailFrom, "alerts@example.com") {
		t.Errorf("MAIL FROM: %q", srv.mailFrom)
	}
	if !strings.Contains(srv.rcptTo, "ops@example.com") {
		t.Errorf("RCPT TO: %q", srv.rcptTo)
	}
	if !strings.Contains(srv.data, "Subject: [restic-manager] [raised · warning] alfa-01: backup_failed") {
		t.Errorf("subject missing or wrong: %q", srv.data)
	}
	if !strings.Contains(srv.data, "Message-ID: <01ABC@rm.example>") {
		t.Errorf("Message-ID wrong: %q", srv.data)
	}
	if !strings.Contains(srv.data, "Backup failed: 401") {
		t.Errorf("body missing: %q", srv.data)
	}
}

func splitHostPort(addr string) (string, int) {
	host, portStr, _ := net.SplitHostPort(addr)
	var port int
	for _, r := range portStr {
		port = port*10 + int(r-'0')
	}
	return host, port
}
