package email

import (
	"context"
	"os"
	"strings"
	"testing"

	"github.com/nexuscrm/backend/internal/domain/ports"
	"github.com/nexuscrm/backend/pkg/logging"
	"github.com/stretchr/testify/assert"
)

// ---------------------------------------------------------------------------
// buildMIMEMessage
// ---------------------------------------------------------------------------

func TestBuildMIMEMessage_PlainOnly(t *testing.T) {
	msg := &ports.EmailMessage{
		To:      []string{"alice@example.com"},
		Subject: "Hello",
		Body:    "Plain text body",
	}

	result := buildMIMEMessage("sender@example.com", msg)

	assert.Contains(t, result, "From: sender@example.com\r\n")
	assert.Contains(t, result, "To: alice@example.com\r\n")
	assert.Contains(t, result, "Content-Type: text/plain; charset=utf-8")
	assert.Contains(t, result, "Plain text body")
	assert.NotContains(t, result, "multipart")
}

func TestBuildMIMEMessage_HTMLOnly(t *testing.T) {
	msg := &ports.EmailMessage{
		To:      []string{"alice@example.com"},
		Subject: "Hello",
		HTML:    "<h1>Hello</h1>",
	}

	result := buildMIMEMessage("sender@example.com", msg)

	assert.Contains(t, result, "Content-Type: text/html; charset=utf-8")
	assert.Contains(t, result, "<h1>Hello</h1>")
	assert.NotContains(t, result, "multipart")
}

func TestBuildMIMEMessage_Multipart(t *testing.T) {
	msg := &ports.EmailMessage{
		To:      []string{"alice@example.com"},
		Subject: "Hello",
		Body:    "Plain text",
		HTML:    "<p>HTML text</p>",
	}

	result := buildMIMEMessage("sender@example.com", msg)

	assert.Contains(t, result, "Content-Type: multipart/alternative; boundary=NexusCRM-boundary-001")
	assert.Contains(t, result, "Content-Type: text/plain; charset=utf-8")
	assert.Contains(t, result, "Plain text")
	assert.Contains(t, result, "Content-Type: text/html; charset=utf-8")
	assert.Contains(t, result, "<p>HTML text</p>")
}

func TestBuildMIMEMessage_MultipleRecipients(t *testing.T) {
	msg := &ports.EmailMessage{
		To:      []string{"alice@example.com", "bob@example.com"},
		Cc:      []string{"carol@example.com"},
		Subject: "Team update",
		Body:    "Hi team",
	}

	result := buildMIMEMessage("sender@example.com", msg)

	assert.Contains(t, result, "To: alice@example.com, bob@example.com\r\n")
	assert.Contains(t, result, "Cc: carol@example.com\r\n")
}

func TestBuildMIMEMessage_SubjectEncoding(t *testing.T) {
	msg := &ports.EmailMessage{
		To:      []string{"alice@example.com"},
		Subject: "Héllo Wörld",
		Body:    "test",
	}

	result := buildMIMEMessage("sender@example.com", msg)

	// Subject should be Q-encoded for non-ASCII characters
	assert.Contains(t, result, "Subject: ")
	assert.Contains(t, result, "MIME-Version: 1.0")
}

func TestBuildMIMEMessage_NoCc(t *testing.T) {
	msg := &ports.EmailMessage{
		To:      []string{"alice@example.com"},
		Subject: "No CC",
		Body:    "test",
	}

	result := buildMIMEMessage("sender@example.com", msg)

	assert.NotContains(t, result, "Cc:")
}

// ---------------------------------------------------------------------------
// SMTPSender — construction & no-recipients error
// ---------------------------------------------------------------------------

func TestNewSMTPSender(t *testing.T) {
	s := NewSMTPSender("smtp.example.com", "587", "user", "pass", "from@example.com", true)

	assert.Equal(t, "smtp.example.com", s.host)
	assert.Equal(t, "587", s.port)
	assert.Equal(t, "user", s.user)
	assert.Equal(t, "pass", s.password)
	assert.Equal(t, "from@example.com", s.fromAddr)
	assert.True(t, s.useTLS)
}

func TestSMTPSender_Send_NoRecipients(t *testing.T) {
	s := NewSMTPSender("smtp.example.com", "587", "", "", "from@example.com", false)

	err := s.Send(context.Background(), &ports.EmailMessage{
		Subject: "Test",
		Body:    "body",
	})

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "no recipients")
}

func TestSMTPSender_Send_DialError(t *testing.T) {
	// Use an unreachable address to test dial failure
	s := NewSMTPSender("127.0.0.1", "19999", "", "", "from@example.com", false)

	err := s.Send(context.Background(), &ports.EmailMessage{
		To:      []string{"test@example.com"},
		Subject: "Test",
		Body:    "body",
	})

	assert.Error(t, err)
	assert.Contains(t, err.Error(), "dial")
}

func TestSMTPSender_Send_DefaultFrom(t *testing.T) {
	// Verify that when msg.From is empty, s.fromAddr is used
	s := NewSMTPSender("127.0.0.1", "19999", "", "", "default@example.com", false)

	msg := &ports.EmailMessage{
		To:      []string{"test@example.com"},
		Subject: "Test",
		Body:    "body",
		// From is empty
	}

	// Will fail on dial, but we can verify the MIME message uses default from
	body := buildMIMEMessage(s.fromAddr, msg)
	assert.Contains(t, body, "From: default@example.com")
}

func TestSMTPSender_Send_CustomFrom(t *testing.T) {
	msg := &ports.EmailMessage{
		From:    "custom@example.com",
		To:      []string{"test@example.com"},
		Subject: "Test",
		Body:    "body",
	}

	body := buildMIMEMessage(msg.From, msg)
	assert.Contains(t, body, "From: custom@example.com")
}

// ---------------------------------------------------------------------------
// NoopSender
// ---------------------------------------------------------------------------

func TestNoopSender_Send(t *testing.T) {
	logger := logging.New(context.Background())
	s := NewNoopSender(logger)

	err := s.Send(context.Background(), &ports.EmailMessage{
		To:      []string{"alice@example.com"},
		Subject: "Test Subject",
		Body:    "body",
	})

	assert.NoError(t, err)
}

// ---------------------------------------------------------------------------
// Factory — NewEmailSenderFromEnv
// ---------------------------------------------------------------------------

func TestNewEmailSenderFromEnv_NoHost(t *testing.T) {
	os.Unsetenv("SMTP_HOST")
	logger := logging.New(context.Background())

	sender := NewEmailSenderFromEnv(logger)

	_, isNoop := sender.(*NoopSender)
	assert.True(t, isNoop, "expected NoopSender when SMTP_HOST is not set")
}

func TestNewEmailSenderFromEnv_WithHost(t *testing.T) {
	t.Setenv("SMTP_HOST", "smtp.example.com")
	t.Setenv("SMTP_PORT", "465")
	t.Setenv("SMTP_USER", "user")
	t.Setenv("SMTP_PASSWORD", "secret")
	t.Setenv("SMTP_FROM", "noreply@example.com")
	t.Setenv("SMTP_TLS", "true")

	logger := logging.New(context.Background())
	sender := NewEmailSenderFromEnv(logger)

	smtp, isSMTP := sender.(*SMTPSender)
	assert.True(t, isSMTP, "expected SMTPSender when SMTP_HOST is set")
	assert.Equal(t, "smtp.example.com", smtp.host)
	assert.Equal(t, "465", smtp.port)
	assert.Equal(t, "user", smtp.user)
	assert.Equal(t, "secret", smtp.password)
	assert.Equal(t, "noreply@example.com", smtp.fromAddr)
	assert.True(t, smtp.useTLS)
}

func TestNewEmailSenderFromEnv_Defaults(t *testing.T) {
	t.Setenv("SMTP_HOST", "smtp.example.com")
	os.Unsetenv("SMTP_PORT")
	os.Unsetenv("SMTP_FROM")
	os.Unsetenv("SMTP_TLS")

	logger := logging.New(context.Background())
	sender := NewEmailSenderFromEnv(logger)

	smtp, isSMTP := sender.(*SMTPSender)
	assert.True(t, isSMTP)
	assert.Equal(t, "587", smtp.port, "default port should be 587")
	assert.Equal(t, "noreply@nexuscrm.com", smtp.fromAddr, "default from address")
	assert.True(t, smtp.useTLS, "TLS should default to true")
}

func TestNewEmailSenderFromEnv_TLSFalse(t *testing.T) {
	t.Setenv("SMTP_HOST", "localhost")
	t.Setenv("SMTP_TLS", "false")

	logger := logging.New(context.Background())
	sender := NewEmailSenderFromEnv(logger)

	smtp := sender.(*SMTPSender)
	assert.False(t, smtp.useTLS)
}

func TestNewEmailSenderFromEnv_TLSNumeric(t *testing.T) {
	t.Setenv("SMTP_HOST", "localhost")
	t.Setenv("SMTP_TLS", "1")

	logger := logging.New(context.Background())
	sender := NewEmailSenderFromEnv(logger)

	smtp := sender.(*SMTPSender)
	assert.True(t, smtp.useTLS)
}

// ---------------------------------------------------------------------------
// Collect recipients
// ---------------------------------------------------------------------------

func TestSMTPSender_CollectsAllRecipients(t *testing.T) {
	msg := &ports.EmailMessage{
		To:  []string{"to@example.com"},
		Cc:  []string{"cc@example.com"},
		Bcc: []string{"bcc@example.com"},
	}

	var rcpts []string
	rcpts = append(rcpts, msg.To...)
	rcpts = append(rcpts, msg.Cc...)
	rcpts = append(rcpts, msg.Bcc...)

	assert.Len(t, rcpts, 3)
	assert.True(t, strings.Contains(strings.Join(rcpts, ","), "to@example.com"))
	assert.True(t, strings.Contains(strings.Join(rcpts, ","), "cc@example.com"))
	assert.True(t, strings.Contains(strings.Join(rcpts, ","), "bcc@example.com"))
}
