package services

import (
	"fmt"
	"net/smtp"
	"regexp"
	"strings"
	"testing"
)

// MockSMTPSender is a mock SMTP sender for testing
type MockSMTPSender struct {
	SentEmails []SentEmail
	ShouldFail bool
	FailError  error
}

// SentEmail represents a sent email for testing
type SentEmail struct {
	To      []string
	Subject string
	Body    string
}

// SendMail is a mock implementation of smtp.SendMail
func (m *MockSMTPSender) SendMail(addr string, auth smtp.Auth, from string, to []string, msg []byte) error {
	if m.ShouldFail {
		return m.FailError
	}

	// Parse the email to extract subject and body
	msgStr := string(msg)
	subject := extractSubject(msgStr)

	m.SentEmails = append(m.SentEmails, SentEmail{
		To:      to,
		Subject: subject,
		Body:    msgStr,
	})

	return nil
}

// extractSubject extracts the subject from an email message
func extractSubject(msg string) string {
	lines := strings.Split(msg, "\r\n")
	for _, line := range lines {
		if strings.HasPrefix(line, "Subject: ") {
			return strings.TrimPrefix(line, "Subject: ")
		}
	}
	return ""
}

// TestEmailService_SendEmail tests basic email sending
func TestEmailService_SendEmail(t *testing.T) {
	tests := []struct {
		name        string
		to          string
		subject     string
		htmlBody    string
		textBody    string
		shouldFail  bool
		expectError bool
	}{
		{
			name:        "valid email",
			to:          "user@example.com",
			subject:     "Test Email",
			htmlBody:    "<h1>Hello</h1><p>World</p>",
			textBody:    "Hello\nWorld",
			shouldFail:  false,
			expectError: false,
		},
		{
			name:        "email with special characters",
			to:          "user+test@example.com",
			subject:     "Test: Öäü Special Characters",
			htmlBody:    "<p>Special: €£¥</p>",
			textBody:    "Special: €£¥",
			shouldFail:  false,
			expectError: false,
		},
		{
			name:        "SMTP failure",
			to:          "user@example.com",
			subject:     "Test",
			htmlBody:    "<p>Test</p>",
			textBody:    "Test",
			shouldFail:  true,
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Validate email format
			isValidEmail := strings.Contains(tt.to, "@") && strings.Contains(tt.to, ".")

			if !isValidEmail && !tt.expectError {
				t.Error("Invalid email format should produce error")
			}

			// Validate subject is not empty
			if tt.subject == "" && !tt.expectError {
				t.Error("Empty subject should produce error")
			}

			// Validate body content exists
			if (tt.htmlBody == "" && tt.textBody == "") && !tt.expectError {
				t.Error("Both bodies empty should produce error")
			}

			// Simulate SMTP send
			var err error
			if tt.shouldFail {
				err = fmt.Errorf("SMTP error: connection refused")
			}

			if tt.expectError && err == nil {
				t.Error("Expected error, got nil")
			}
			if !tt.expectError && err != nil {
				t.Errorf("Expected no error, got %v", err)
			}
		})
	}
}

// TestEmailService_SendVerificationEmail tests verification email sending
func TestEmailService_SendVerificationEmail(t *testing.T) {
	tests := []struct {
		name        string
		to          string
		userName    string
		token       string
		expectError bool
	}{
		{
			name:        "valid verification email",
			to:          "newuser@example.com",
			userName:    "Max Mustermann",
			token:       "abc123def456",
			expectError: false,
		},
		{
			name:        "user without name",
			to:          "user@example.com",
			userName:    "",
			token:       "token123",
			expectError: false,
		},
		{
			name:        "empty token",
			to:          "user@example.com",
			userName:    "Test User",
			token:       "",
			expectError: true,
		},
		{
			name:        "invalid email",
			to:          "invalid-email",
			userName:    "Test",
			token:       "token123",
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Validate inputs
			var err error
			if tt.token == "" {
				err = &ValidationError{Field: "token", Message: "required"}
			} else if !strings.Contains(tt.to, "@") {
				err = &ValidationError{Field: "email", Message: "invalid format"}
			}

			// Build verification link
			if tt.token != "" {
				verifyLink := fmt.Sprintf("https://example.com/verify-email?token=%s", tt.token)
				if verifyLink == "" {
					t.Error("Verification link should not be empty")
				}

				// Verify link contains token
				if !strings.Contains(verifyLink, tt.token) {
					t.Error("Verification link should contain token")
				}
			}

			if tt.expectError && err == nil {
				t.Error("Expected error, got nil")
			}
			if !tt.expectError && err != nil {
				t.Errorf("Expected no error, got %v", err)
			}
		})
	}
}

// TestEmailService_SendPasswordResetEmail tests password reset email
func TestEmailService_SendPasswordResetEmail(t *testing.T) {
	tests := []struct {
		name        string
		to          string
		userName    string
		token       string
		expectError bool
	}{
		{
			name:        "valid password reset",
			to:          "user@example.com",
			userName:    "John Doe",
			token:       "reset-token-123",
			expectError: false,
		},
		{
			name:        "empty token",
			to:          "user@example.com",
			userName:    "John Doe",
			token:       "",
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var err error
			if tt.token == "" {
				err = &ValidationError{Field: "token", Message: "required"}
			}

			// Build reset link
			if tt.token != "" {
				resetLink := fmt.Sprintf("https://example.com/reset-password?token=%s", tt.token)

				// Verify link is secure (HTTPS)
				if !strings.HasPrefix(resetLink, "https://") {
					t.Error("Reset link should use HTTPS")
				}
			}

			if tt.expectError && err == nil {
				t.Error("Expected error, got nil")
			}
			if !tt.expectError && err != nil {
				t.Errorf("Expected no error, got %v", err)
			}
		})
	}
}

// TestEmailService_Send2FAEmail tests 2FA notification emails
func TestEmailService_Send2FAEmail(t *testing.T) {
	tests := []struct {
		name        string
		to          string
		action      string
		expectError bool
	}{
		{
			name:        "2FA enabled notification",
			to:          "user@example.com",
			action:      "enabled",
			expectError: false,
		},
		{
			name:        "2FA disabled notification",
			to:          "user@example.com",
			action:      "disabled",
			expectError: false,
		},
		{
			name:        "invalid action",
			to:          "user@example.com",
			action:      "invalid",
			expectError: true,
		},
	}

	validActions := map[string]bool{
		"enabled":  true,
		"disabled": true,
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var err error
			if !validActions[tt.action] {
				err = &ValidationError{Field: "action", Message: "must be 'enabled' or 'disabled'"}
			}

			if tt.expectError && err == nil {
				t.Error("Expected error, got nil")
			}
			if !tt.expectError && err != nil {
				t.Errorf("Expected no error, got %v", err)
			}
		})
	}
}

// TestEmailService_SendConsentReminderEmail tests consent reminder
func TestEmailService_SendConsentReminderEmail(t *testing.T) {
	tests := []struct {
		name         string
		to           string
		documentName string
		daysLeft     int
		expectError  bool
	}{
		{
			name:         "reminder with 7 days left",
			to:           "user@example.com",
			documentName: "Terms of Service",
			daysLeft:     7,
			expectError:  false,
		},
		{
			name:         "reminder with 1 day left",
			to:           "user@example.com",
			documentName: "Privacy Policy",
			daysLeft:     1,
			expectError:  false,
		},
		{
			name:         "urgent reminder - overdue",
			to:           "user@example.com",
			documentName: "Terms",
			daysLeft:     0,
			expectError:  false,
		},
		{
			name:         "empty document name",
			to:           "user@example.com",
			documentName: "",
			daysLeft:     7,
			expectError:  true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var err error
			if tt.documentName == "" {
				err = &ValidationError{Field: "document name", Message: "required"}
			}

			// Check urgency level
			var urgency string
			if tt.daysLeft <= 0 {
				urgency = "critical"
			} else if tt.daysLeft <= 3 {
				urgency = "urgent"
			} else {
				urgency = "normal"
			}

			if urgency == "" && !tt.expectError {
				t.Error("Urgency should be set")
			}

			if tt.expectError && err == nil {
				t.Error("Expected error, got nil")
			}
			if !tt.expectError && err != nil {
				t.Errorf("Expected no error, got %v", err)
			}
		})
	}
}

// TestEmailService_MIMEFormatting tests MIME message formatting
func TestEmailService_MIMEFormatting(t *testing.T) {
	tests := []struct {
		name     string
		htmlBody string
		textBody string
		checkFor []string
	}{
		{
			name:     "multipart alternative",
			htmlBody: "<h1>Test</h1>",
			textBody: "Test",
			checkFor: []string{
				"MIME-Version: 1.0",
				"Content-Type: multipart/alternative",
				"Content-Type: text/plain",
				"Content-Type: text/html",
			},
		},
		{
			name:     "UTF-8 encoding",
			htmlBody: "<p>Öäü</p>",
			textBody: "Öäü",
			checkFor: []string{
				"charset=\"UTF-8\"",
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Build MIME message (simplified)
			message := fmt.Sprintf("MIME-Version: 1.0\r\n"+
				"Content-Type: multipart/alternative; boundary=\"boundary\"\r\n"+
				"\r\n"+
				"--boundary\r\n"+
				"Content-Type: text/plain; charset=\"UTF-8\"\r\n"+
				"\r\n%s\r\n"+
				"--boundary\r\n"+
				"Content-Type: text/html; charset=\"UTF-8\"\r\n"+
				"\r\n%s\r\n"+
				"--boundary--\r\n",
				tt.textBody, tt.htmlBody)

			// Verify required headers are present
			for _, required := range tt.checkFor {
				if !strings.Contains(message, required) {
					t.Errorf("Message should contain '%s'", required)
				}
			}

			// Verify both bodies are included
			if !strings.Contains(message, tt.textBody) {
				t.Error("Message should contain text body")
			}
			if !strings.Contains(message, tt.htmlBody) {
				t.Error("Message should contain HTML body")
			}
		})
	}
}

// TestEmailService_TemplateRendering tests email template rendering
func TestEmailService_TemplateRendering(t *testing.T) {
	tests := []struct {
		name       string
		template   string
		variables  map[string]string
		expectVars []string
	}{
		{
			name:     "verification template",
			template: "verification",
			variables: map[string]string{
				"Name":       "John Doe",
				"VerifyLink": "https://example.com/verify?token=abc",
			},
			expectVars: []string{"John Doe", "https://example.com/verify?token=abc"},
		},
		{
			name:     "password reset template",
			template: "password_reset",
			variables: map[string]string{
				"Name":      "Jane Smith",
				"ResetLink": "https://example.com/reset?token=xyz",
			},
			expectVars: []string{"Jane Smith", "https://example.com/reset?token=xyz"},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Simulate template rendering
			rendered := fmt.Sprintf("Hello %s, please visit %s",
				tt.variables["Name"],
				getLink(tt.variables))

			// Verify all variables are in rendered output
			for _, expectedVar := range tt.expectVars {
				if !strings.Contains(rendered, expectedVar) {
					t.Errorf("Rendered template should contain '%s'", expectedVar)
				}
			}
		})
	}
}

// TestEmailService_EmailValidation tests email address validation
func TestEmailService_EmailValidation(t *testing.T) {
	tests := []struct {
		email   string
		isValid bool
	}{
		{"user@example.com", true},
		{"user+tag@example.com", true},
		{"user.name@example.co.uk", true},
		{"user@subdomain.example.com", true},
		{"invalid", false},
		{"@example.com", false},
		{"user@", false},
		{"user@.com", false},
		{"", false},
	}

	for _, tt := range tests {
		t.Run(tt.email, func(t *testing.T) {
			// RFC 5322 compliant email validation pattern
			emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
			isValid := emailRegex.MatchString(tt.email)

			if isValid != tt.isValid {
				t.Errorf("Email %s: expected valid=%v, got %v", tt.email, tt.isValid, isValid)
			}
		})
	}
}

// TestEmailService_SMTPConfig tests SMTP configuration
func TestEmailService_SMTPConfig(t *testing.T) {
	tests := []struct {
		name        string
		config      EmailConfig
		expectError bool
	}{
		{
			name: "valid config",
			config: EmailConfig{
				Host:     "smtp.example.com",
				Port:     587,
				Username: "user@example.com",
				Password: "password",
				FromName: "BreakPilot",
				FromAddr: "noreply@example.com",
				BaseURL:  "https://example.com",
			},
			expectError: false,
		},
		{
			name: "missing host",
			config: EmailConfig{
				Port:     587,
				Username: "user@example.com",
				Password: "password",
			},
			expectError: true,
		},
		{
			name: "invalid port",
			config: EmailConfig{
				Host:     "smtp.example.com",
				Port:     0,
				Username: "user@example.com",
				Password: "password",
			},
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var err error
			if tt.config.Host == "" {
				err = &ValidationError{Field: "host", Message: "required"}
			} else if tt.config.Port <= 0 || tt.config.Port > 65535 {
				err = &ValidationError{Field: "port", Message: "must be between 1 and 65535"}
			}

			if tt.expectError && err == nil {
				t.Error("Expected error, got nil")
			}
			if !tt.expectError && err != nil {
				t.Errorf("Expected no error, got %v", err)
			}
		})
	}
}

// TestEmailService_RateLimiting tests email rate limiting logic
func TestEmailService_RateLimiting(t *testing.T) {
	tests := []struct {
		name           string
		emailsSent     int
		timeWindow     int // minutes
		limit          int
		expectThrottle bool
	}{
		{
			name:           "under limit",
			emailsSent:     5,
			timeWindow:     60,
			limit:          10,
			expectThrottle: false,
		},
		{
			name:           "at limit",
			emailsSent:     10,
			timeWindow:     60,
			limit:          10,
			expectThrottle: false,
		},
		{
			name:           "over limit",
			emailsSent:     15,
			timeWindow:     60,
			limit:          10,
			expectThrottle: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			shouldThrottle := tt.emailsSent > tt.limit

			if shouldThrottle != tt.expectThrottle {
				t.Errorf("Expected throttle=%v, got %v", tt.expectThrottle, shouldThrottle)
			}
		})
	}
}

// Helper functions

func getLink(vars map[string]string) string {
	if link, ok := vars["VerifyLink"]; ok {
		return link
	}
	if link, ok := vars["ResetLink"]; ok {
		return link
	}
	return ""
}
