package notify

import (
	"context"
	"errors"
	"net/smtp"
	"strings"
	"sync"
	"testing"

	"github.com/google/uuid"
	"github.com/stretchr/testify/require"
)

// fakePrefsRepo 是 PrefsRepository 的内存实现，供 notifier 测试断言偏好过滤。
type fakePrefsRepo struct {
	mu  sync.Mutex
	m   map[uuid.UUID]Prefs
	err error
}

func newFakePrefs() *fakePrefsRepo { return &fakePrefsRepo{m: map[uuid.UUID]Prefs{}} }

func (f *fakePrefsRepo) set(p Prefs) {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.m[p.UserID] = p
}

func (f *fakePrefsRepo) Get(_ context.Context, userID uuid.UUID) (Prefs, error) {
	if f.err != nil {
		return Prefs{}, f.err
	}
	f.mu.Lock()
	defer f.mu.Unlock()
	if p, ok := f.m[userID]; ok {
		return p, nil
	}
	return DefaultPrefs(userID), nil
}

func (f *fakePrefsRepo) Upsert(_ context.Context, p Prefs) error {
	if f.err != nil {
		return f.err
	}
	f.set(p)
	return nil
}

// captureTransport 记录最后一次 SMTP 发送，便于断言收件人 / 报文形态。
type captureTransport struct {
	mu    sync.Mutex
	calls int
	addr  string
	from  string
	to    []string
	msg   []byte
	auth  smtp.Auth
}

func (c *captureTransport) Send(addr string, auth smtp.Auth, from string, to []string, msg []byte) error {
	c.mu.Lock()
	defer c.mu.Unlock()
	c.calls++
	c.addr = addr
	c.from = from
	c.to = to
	c.msg = msg
	c.auth = auth
	return nil
}

// staticLookup 总是返回固定收件邮箱。
type staticLookup struct {
	email string
	err   error
}

func (s staticLookup) EmailForUser(context.Context, uuid.UUID) (string, error) {
	return s.email, s.err
}

func emailCfg() EmailConfig {
	return EmailConfig{Enabled: true, SMTPHost: "smtp.test", Port: 587, From: "noreply@test"}
}

func TestEmailNotifier_SendsWhenEnabledAndSeverityMet(t *testing.T) {
	uid := uuid.New()
	prefs := newFakePrefs()
	prefs.set(Prefs{UserID: uid, EmailEnabled: true, MinSeverity: SeverityWarning})
	tr := &captureTransport{}
	n := NewEmailNotifier(emailCfg(), prefs, staticLookup{email: "user@test"}, tr, newTestLogger())

	err := n.Send(context.Background(), Message{
		UserID: uid, Topic: "acp.permission_pending", Severity: SeverityWarning,
		Title: "Approval needed", Body: "session X wants to run rm", Link: "/approvals",
	})
	require.NoError(t, err)
	require.Equal(t, 1, tr.calls)
	require.Equal(t, "smtp.test:587", tr.addr)
	require.Equal(t, "noreply@test", tr.from)
	require.Equal(t, []string{"user@test"}, tr.to)
	require.Nil(t, tr.auth, "无 username 时不应携带 auth")

	body := string(tr.msg)
	require.Contains(t, body, "To: user@test")
	require.Contains(t, body, "Subject: [WARNING] Approval needed")
	require.Contains(t, body, "session X wants to run rm")
	require.Contains(t, body, "/approvals")
}

func TestEmailNotifier_DisabledChannelSkipped(t *testing.T) {
	uid := uuid.New()
	prefs := newFakePrefs()
	prefs.set(Prefs{UserID: uid, EmailEnabled: false, MinSeverity: SeverityInfo})
	tr := &captureTransport{}
	n := NewEmailNotifier(emailCfg(), prefs, staticLookup{email: "user@test"}, tr, newTestLogger())

	require.NoError(t, n.Send(context.Background(), Message{UserID: uid, Severity: SeverityError}))
	require.Equal(t, 0, tr.calls, "email_enabled=false 应跳过发送")
}

func TestEmailNotifier_BelowMinSeveritySkipped(t *testing.T) {
	uid := uuid.New()
	prefs := newFakePrefs()
	prefs.set(Prefs{UserID: uid, EmailEnabled: true, MinSeverity: SeverityError})
	tr := &captureTransport{}
	n := NewEmailNotifier(emailCfg(), prefs, staticLookup{email: "user@test"}, tr, newTestLogger())

	require.NoError(t, n.Send(context.Background(), Message{UserID: uid, Severity: SeverityWarning}))
	require.Equal(t, 0, tr.calls, "低于 min_severity 应跳过")

	require.NoError(t, n.Send(context.Background(), Message{UserID: uid, Severity: SeverityError}))
	require.Equal(t, 1, tr.calls, "达到 min_severity 应发送")
}

func TestEmailNotifier_GloballyDisabledSkipped(t *testing.T) {
	uid := uuid.New()
	prefs := newFakePrefs()
	prefs.set(Prefs{UserID: uid, EmailEnabled: true, MinSeverity: SeverityInfo})
	tr := &captureTransport{}
	cfg := emailCfg()
	cfg.Enabled = false
	n := NewEmailNotifier(cfg, prefs, staticLookup{email: "user@test"}, tr, newTestLogger())

	require.NoError(t, n.Send(context.Background(), Message{UserID: uid, Severity: SeverityError}))
	require.Equal(t, 0, tr.calls)
}

func TestEmailNotifier_NoRecipientSkipped(t *testing.T) {
	uid := uuid.New()
	prefs := newFakePrefs()
	prefs.set(Prefs{UserID: uid, EmailEnabled: true, MinSeverity: SeverityInfo})
	tr := &captureTransport{}
	n := NewEmailNotifier(emailCfg(), prefs, staticLookup{email: "  "}, tr, newTestLogger())

	require.NoError(t, n.Send(context.Background(), Message{UserID: uid, Severity: SeverityError}))
	require.Equal(t, 0, tr.calls, "空收件人应静默跳过")
}

func TestEmailNotifier_UsesPlainAuthWhenUsernameSet(t *testing.T) {
	uid := uuid.New()
	prefs := newFakePrefs()
	prefs.set(Prefs{UserID: uid, EmailEnabled: true, MinSeverity: SeverityInfo})
	tr := &captureTransport{}
	cfg := emailCfg()
	cfg.Username = "u"
	cfg.Password = "p"
	n := NewEmailNotifier(cfg, prefs, staticLookup{email: "user@test"}, tr, newTestLogger())

	require.NoError(t, n.Send(context.Background(), Message{UserID: uid, Severity: SeverityError, Title: "x"}))
	require.Equal(t, 1, tr.calls)
	require.NotNil(t, tr.auth, "配置 username 时应携带 PlainAuth")
}

func TestEmailNotifier_NameIsEmailChannel(t *testing.T) {
	n := NewEmailNotifier(emailCfg(), newFakePrefs(), staticLookup{}, &captureTransport{}, newTestLogger())
	require.Equal(t, ChannelEmail, n.Name())
}

func TestBuildEmailMessage_Headers(t *testing.T) {
	body := string(buildEmailMessage("from@test", "to@test", Message{
		Topic: "t", Severity: SeverityError, Title: "Title", Body: "Body", Link: "http://l",
	}))
	require.True(t, strings.HasPrefix(body, "From: from@test\r\n"))
	require.Contains(t, body, "Subject: [ERROR] Title")
	require.Contains(t, body, "Content-Type: text/plain")
}

func TestEmailNotifier_PrefsErrorPropagates(t *testing.T) {
	prefs := newFakePrefs()
	prefs.err = errors.New("db down")
	n := NewEmailNotifier(emailCfg(), prefs, staticLookup{email: "u@t"}, &captureTransport{}, newTestLogger())
	require.Error(t, n.Send(context.Background(), Message{UserID: uuid.New(), Severity: SeverityError}))
}
