package util

import (
	"fmt"
	"io"
	"net/mail"
	"os"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

func validateEmailMessage(t *testing.T, rawMessage string, wantEmptyBody bool) {
	t.Helper()

	msg, err := mail.ReadMessage(strings.NewReader(rawMessage))
	if err != nil {
		t.Errorf("Failed to parse email message: %v", err)
		return
	}

	requiredHeaders := []string{"From", "To", "Subject", "MIME-Version", "Content-Type"}
	for _, header := range requiredHeaders {
		if msg.Header.Get(header) == "" {
			t.Errorf("Missing required header: %s", header)
		}
	}

	body, err := io.ReadAll(msg.Body)
	if err != nil {
		t.Errorf("Failed to read body from parsed message: %v", err)
	}
	if !wantEmptyBody && len(body) == 0 {
		t.Errorf("Empty body")
	}
}

func TestEmailRecipient_NameAndEmail(t *testing.T) {
	tests := []struct {
		recipient EmailRecipient
		want      string
	}{
		{recipient: EmailRecipient{Email: ""}, want: ""},
		{recipient: EmailRecipient{Email: "john.doe@example.com", FirstName: "John", LastName: "Doe"}, want: "\"John Doe\" <john.doe@example.com>"},
		{recipient: EmailRecipient{Email: "john@example.com", FirstName: "John"}, want: "\"John\" <john@example.com>"},
		{recipient: EmailRecipient{Email: "doe@example.com", LastName: "Doe"}, want: "\"Doe\" <doe@example.com>"},
		{recipient: EmailRecipient{Email: "test@example.com"}, want: "<test@example.com>"},
		{recipient: EmailRecipient{Email: "john.doe@example.com", FirstName: "John", LastName: "Doe Smith"}, want: "\"John Doe Smith\" <john.doe@example.com>"},
		{recipient: EmailRecipient{Email: "rene@example.com", FirstName: "René"}, want: "=?utf-8?q?Ren=C3=A9?= <rene@example.com>"},
		{recipient: EmailRecipient{Email: "john.garcia@example.com", FirstName: "John", LastName: "García"}, want: "=?utf-8?q?John_Garc=C3=ADa?= <john.garcia@example.com>"},
		{recipient: EmailRecipient{Email: "party@example.com", FirstName: "🎉"}, want: "=?utf-8?q?=F0=9F=8E=89?= <party@example.com>"},
	}

	for _, tt := range tests {
		if got := tt.recipient.NameAndEmail(); got != tt.want {
			t.Errorf("NameAndEmail() = %q, want %q", got, tt.want)
		}
	}
}

func TestIsLastDayOfTheMonth(t *testing.T) {
	tests := []struct {
		name string
		date time.Time
		want bool
	}{
		{
			name: "last day of January (31st)",
			date: time.Date(2023, time.January, 31, 10, 30, 0, 0, time.UTC),
			want: true,
		},
		{
			name: "not last day of January (30th)",
			date: time.Date(2023, time.January, 30, 10, 30, 0, 0, time.UTC),
			want: false,
		},
		{
			name: "last day of February in non-leap year (28th)",
			date: time.Date(2023, time.February, 28, 10, 30, 0, 0, time.UTC),
			want: true,
		},
		{
			name: "not last day of February in non-leap year (27th)",
			date: time.Date(2023, time.February, 27, 10, 30, 0, 0, time.UTC),
			want: false,
		},
		{
			name: "last day of February in leap year (29th)",
			date: time.Date(2024, time.February, 29, 10, 30, 0, 0, time.UTC),
			want: true,
		},
		{
			name: "not last day of February in leap year (28th)",
			date: time.Date(2024, time.February, 28, 10, 30, 0, 0, time.UTC),
			want: false,
		},
		{
			name: "first day of month (1st)",
			date: time.Date(2023, time.March, 1, 10, 30, 0, 0, time.UTC),
			want: false,
		},
		{
			name: "middle of month (15th)",
			date: time.Date(2023, time.March, 15, 10, 30, 0, 0, time.UTC),
			want: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := IsLastDayOfTheMonth(tt.date)
			if result != tt.want {
				t.Errorf("IsLastDayOfTheMonth(%v) = %v, want %v",
					tt.date, result, tt.want)
			}
		})
	}
}

func TestParseBool(t *testing.T) {
	tests := []struct {
		value     string
		want      bool
		wantError bool
	}{
		// Accepted values
		{
			value:     "yes",
			want:      true,
			wantError: false,
		},
		{
			value:     "YES",
			want:      true,
			wantError: false,
		},
		{
			value:     "Yes",
			want:      true,
			wantError: false,
		},
		{
			value:     "no",
			want:      false,
			wantError: false,
		},
		{
			value:     "NO",
			want:      false,
			wantError: false,
		},
		{
			value:     "No",
			want:      false,
			wantError: false,
		},
		{
			value:     "true",
			want:      true,
			wantError: false,
		},
		{
			value:     "false",
			want:      false,
			wantError: false,
		},
		{
			value:     "1",
			want:      true,
			wantError: false,
		},
		{
			value:     "0",
			want:      false,
			wantError: false,
		},
		{
			value:     "t",
			want:      true,
			wantError: false,
		},
		{
			value:     "f",
			want:      false,
			wantError: false,
		},
		{
			value:     "TRUE",
			want:      true,
			wantError: false,
		},
		{
			value:     "FALSE",
			want:      false,
			wantError: false,
		},

		// Errors
		{
			value:     "invalid",
			want:      false,
			wantError: true,
		},
		{
			value:     "",
			want:      false,
			wantError: true,
		},
		{
			value:     "123",
			want:      false,
			wantError: true,
		},
		{
			value:     "   ",
			want:      false,
			wantError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.value, func(t *testing.T) {
			result, err := ParseBool(tt.value)

			if tt.wantError {
				if err == nil {
					t.Errorf("ParseBool(%q) expected error, but got none", tt.value)
				}
			} else {
				if err != nil {
					t.Errorf("ParseBool(%q) unexpected error: %v", tt.value, err)
				}
			}

			if result != tt.want {
				t.Errorf("ParseBool(%q) = %v, want %v", tt.value, result, tt.want)
			}
		})
	}
}

func TestValidateEmail(t *testing.T) {
	tests := []struct {
		email     string
		wantError bool
	}{
		// Valid email addresses (RFC5322)
		{
			email:     "root@localhost",
			wantError: false,
		},
		{
			email:     "admin@mail.example.com",
			wantError: false,
		},
		{
			email:     "user123@example.com",
			wantError: false,
		},
		{
			email:     "first.last@example.com",
			wantError: false,
		},
		{
			email:     "user+tag@example.com",
			wantError: false,
		},
		{
			email:     "user_name@example.com",
			wantError: false,
		},

		// Valid local-only addresses (for local Postfix delivery)
		{
			email:     "user123",
			wantError: false,
		},
		{
			email:     "first.last",
			wantError: false,
		},
		{
			email:     "user_name",
			wantError: false,
		},
		{
			email:     "test-user",
			wantError: false,
		},
		{
			email:     "mail.example.com", // becomes mail.example.com@localhost
			wantError: false,
		},
		{
			email:     "domain!user", // becomes user@domain (legacy UUCP mapping)
			wantError: false,
		},

		// Some mail servers accept emails without a local part
		{
			email:     "@localhost",
			wantError: false,
		},
		{
			email:     "@mail.example.com",
			wantError: false,
		},

		// Invalid email addresses
		{
			email:     "",
			wantError: true,
		},
		{
			email:     "root@",
			wantError: true,
		},
		{
			email:     "@",
			wantError: true,
		},
		{
			email:     "user @example.com",
			wantError: true,
		},
		{
			email:     "user@example.com\nBcc: attacker@evil.com",
			wantError: true,
		},
		{
			email:     "user@example.com\rBcc: attacker@evil.com",
			wantError: true,
		},
		{
			email:     "user@example.com\x00",
			wantError: true,
		},
		{
			email:     "user@@example.com",
			wantError: true,
		},
		{
			email:     "John Doe <john@example.com>",
			wantError: true,
		},

		// Length boundary tests
		{
			// 254 bytes: valid (maximum allowed length)
			email:     fmt.Sprintf("%s@%s.com", strings.Repeat("a", 64), strings.Repeat("b", 185)),
			wantError: false,
		},
		{
			// 255 bytes: invalid (exceeds maximum allowed length)
			email:     fmt.Sprintf("%s@%s.com", strings.Repeat("a", 64), strings.Repeat("b", 186)),
			wantError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.email, func(t *testing.T) {
			err := ValidateEmail(tt.email)

			if tt.wantError {
				if err == nil {
					t.Errorf("ValidateEmail(%q) expected error, but got none", tt.email)
				}
			} else {
				if err != nil {
					t.Errorf("ValidateEmail(%q) unexpected error: %v", tt.email, err)
				}
			}
		})
	}
}

func TestAssembleRawMessage(t *testing.T) {
	const (
		defaultVersion     = "0.0.1"
		fixedHeadersFormat = "X-Auto-Response-Suppress: All\r\n" +
			"Content-Language: en\r\n" +
			"User-Agent: email-reminder/%s\r\n" +
			"MIME-Version: 1.0\r\n" +
			"Content-Type: text/plain; charset=UTF-8\r\n" +
			"Content-Disposition: inline\r\n" +
			"Content-Transfer-Encoding: quoted-printable\r\n"
	)
	tests := []struct {
		name       string
		body       string
		subject    string
		recipient  string
		sender     string
		appVersion string
		want       string
	}{
		{
			name:       "simple message",
			body:       "This is a test message.",
			subject:    "Test Subject",
			recipient:  "test@example.com",
			sender:     "sender@example.com",
			appVersion: defaultVersion,
			want: fmt.Sprintf(
				"From: Email-Reminder <sender@example.com>\r\n"+
					"To: test@example.com\r\n"+
					"Subject: Test Subject\r\n"+
					fixedHeadersFormat+
					"\r\nThis is a test message.",
				defaultVersion,
			),
		},
		{
			name:       "multi-line body",
			body:       "Line 1\nLine 2\nLine 3",
			subject:    "Multi-line",
			recipient:  "test@example.com",
			sender:     "sender@example.com",
			appVersion: defaultVersion,
			want: fmt.Sprintf(
				"From: Email-Reminder <sender@example.com>\r\n"+
					"To: test@example.com\r\n"+
					"Subject: Multi-line\r\n"+
					fixedHeadersFormat+
					"\r\nLine 1\r\nLine 2\r\nLine 3",
				defaultVersion,
			),
		},
		{
			name:       "unicode subject",
			body:       "Test with unicode subject",
			subject:    "Tëst Sübject with émojis 🎉",
			recipient:  "test@example.com",
			sender:     "sender@example.com",
			appVersion: defaultVersion,
			want: fmt.Sprintf(
				"From: Email-Reminder <sender@example.com>\r\n"+
					"To: test@example.com\r\n"+
					"Subject: =?UTF-8?q?T=C3=ABst_S=C3=BCbject_with_=C3=A9mojis_=F0=9F=8E=89?=\r\n"+
					fixedHeadersFormat+
					"\r\nTest with unicode subject",
				defaultVersion,
			),
		},
		{
			name:       "special characters in body",
			body:       "Body with special chars: äöü & < > \"",
			subject:    "Special Chars",
			recipient:  "test@example.com",
			sender:     "sender@example.com",
			appVersion: defaultVersion,
			want: fmt.Sprintf(
				"From: Email-Reminder <sender@example.com>\r\n"+
					"To: test@example.com\r\n"+
					"Subject: Special Chars\r\n"+
					fixedHeadersFormat+
					"\r\nBody with special chars: =C3=A4=C3=B6=C3=BC & < > \"",
				defaultVersion,
			),
		},
		{
			name:       "empty body",
			body:       "",
			subject:    "Empty Body",
			recipient:  "test@example.com",
			sender:     "sender@example.com",
			appVersion: defaultVersion,
			want: fmt.Sprintf(
				"From: Email-Reminder <sender@example.com>\r\n"+
					"To: test@example.com\r\n"+
					"Subject: Empty Body\r\n"+
					fixedHeadersFormat+
					"\r\n",
				defaultVersion,
			),
		},
		{
			name:       "long line that needs quoted-printable encoding",
			body:       "This is a very long line that exceeds the typical 76 character limit for email lines and should be properly encoded using quoted-printable encoding to ensure proper email delivery.",
			subject:    "Long Line Test",
			recipient:  "test@example.com",
			sender:     "sender@example.com",
			appVersion: "1.2.3-test",
			want: fmt.Sprintf(
				"From: Email-Reminder <sender@example.com>\r\n"+
					"To: test@example.com\r\n"+
					"Subject: Long Line Test\r\n"+
					fixedHeadersFormat+
					"\r\nThis is a very long line that exceeds the typical 76 character limit for em=\r\n"+
					"ail lines and should be properly encoded using quoted-printable encoding to=\r\n"+
					" ensure proper email delivery.",
				"1.2.3-test",
			),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := AssembleRawMessage(tt.body, tt.subject, tt.recipient, tt.sender, tt.appVersion)

			// Validate that the result is a properly formatted email message
			validateEmailMessage(t, result, len(tt.body) == 0)

			// Also check that it matches expected output
			if result != tt.want {
				t.Errorf("AssembleRawMessage() = %q, want %q", result, tt.want)
			}
		})
	}
}

func TestValidateHomeDirectory(t *testing.T) {
	tempDir := t.TempDir()

	// Test case 1: Valid directory
	err := ValidateHomeDirectory(tempDir, uint32(os.Getuid()))
	if err != nil {
		t.Errorf("ValidateHomeDirectory() for valid directory failed: %v", err)
	}

	// Test case 2: Non-existent directory
	err = ValidateHomeDirectory("/non/existent/path", uint32(os.Getuid()))
	if err == nil {
		t.Error("ValidateHomeDirectory() should fail for non-existent directory")
	}

	// Test case 3: Create a symlink to test symlink detection
	symlinkPath := filepath.Join(tempDir, "symlink")
	targetPath := filepath.Join(tempDir, "target")
	os.Mkdir(targetPath, 0755)
	err = os.Symlink(targetPath, symlinkPath)
	if err != nil {
		t.Fatalf("Failed to create test symlink: %v", err)
	}

	err = ValidateHomeDirectory(symlinkPath, uint32(os.Getuid()))
	if err == nil {
		t.Error("ValidateHomeDirectory() should fail for symlink")
	}
	if !strings.Contains(err.Error(), "symlink") {
		t.Errorf("Error message should mention symlink, got: %v", err)
	}

	// Test case 4: Wrong owner (we can't easily test this without root permissions,
	// but we can test the error handling by passing an invalid UID)
	err = ValidateHomeDirectory(tempDir, uint32(0xFFFFFFFF)) // Use max uint32 as invalid UID
	if err == nil {
		t.Error("ValidateHomeDirectory() should fail for wrong owner")
	}
}

func TestValidateOpenFile(t *testing.T) {
	// Test case 1: Correctly owned regular file
	tempFile := filepath.Join(t.TempDir(), "testfile")
	file, err := os.Create(tempFile)
	if err != nil {
		t.Fatalf("Failed to create test file: %v", err)
	}
	defer file.Close()

	err = ValidateOpenFile(file, uint32(os.Getuid()))
	if err != nil {
		t.Errorf("ValidateOpenFile() for correctly owned file failed: %v", err)
	}

	// Test case 2: Wrong owner
	err = ValidateOpenFile(file, uint32(0xFFFFFFFF)) // Use max uint32 as invalid UID
	if err == nil {
		t.Error("ValidateOpenFile() should fail for wrong owner")
	}
	if !strings.Contains(err.Error(), "not owned by uid") {
		t.Errorf("Error message should mention ownership, got: %v", err)
	}

	// Test case 3: Not a regular file (directory)
	tempDir := t.TempDir()
	dirFile, err := os.Open(tempDir)
	if err != nil {
		t.Fatalf("Failed to open temp directory: %v", err)
	}
	defer dirFile.Close()

	err = ValidateOpenFile(dirFile, uint32(os.Getuid()))
	if err == nil {
		t.Error("ValidateOpenFile() should fail for directory")
	}
	if !strings.Contains(err.Error(), "not a regular file") {
		t.Errorf("Error message should mention regular file, got: %v", err)
	}
}

func TestFullBody(t *testing.T) {
	tests := []struct {
		name      string
		body      string
		recipient EmailRecipient
		version   string
		want      string
	}{
		{
			name:      "without a name",
			body:      "Your appointment is scheduled for 2pm.",
			recipient: EmailRecipient{Email: "someone@example.com"},
			version:   "0.8.3",
			want: `Hi there,

Your appointment is scheduled for 2pm.

Have a good day!

--
Sent by Email-Reminder 0.8.3
https://launchpad.net/email-reminder`,
		},
		{
			name:      "with only a last name",
			body:      "Test message",
			recipient: EmailRecipient{FirstName: "", LastName: "Smith", Email: "smith@example.com"},
			version:   "1.0.0",
			want: `Hi there,

Test message

Have a good day!

--
Sent by Email-Reminder 1.0.0
https://launchpad.net/email-reminder`,
		},
		{
			name:      "with name and multiline body",
			body:      "Line 1\nLine 2\nLine 3",
			recipient: EmailRecipient{FirstName: "Alice", Email: "alice@example.com"},
			version:   "0.8.3",
			want: `Hi Alice,

Line 1
Line 2
Line 3

Have a good day!

--
Sent by Email-Reminder 0.8.3
https://launchpad.net/email-reminder`,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := FullBody(tt.body, tt.recipient, tt.version)
			if got != tt.want {
				t.Errorf("FullBody() = %q, want %q", got, tt.want)
			}
		})
	}
}

func TestSecureExecCommand(t *testing.T) {
	// Test with a legitimate system binary (should exist and be owned by root)
	t.Run("valid_system_binary", func(t *testing.T) {
		// Try /bin/echo which should be safe and available on most systems
		output, err := SecureExecCommand("/bin/echo", "test")
		if err != nil {
			// This might fail if /bin/echo doesn't exist or isn't owned by root
			// which is acceptable for this security-focused test
			t.Logf("Expected error for system binary (may not be root-owned in test environment): %v", err)
			return
		}
		if len(output) == 0 {
			t.Error("Expected non-empty output from /bin/echo")
		}
	})

	t.Run("nonexistent_binary", func(t *testing.T) {
		_, err := SecureExecCommand("/nonexistent/binary")
		if err == nil {
			t.Error("Expected error for nonexistent binary")
		}
		if !strings.Contains(err.Error(), "cannot stat executable") {
			t.Errorf("Expected 'cannot stat executable' error, got: %v", err)
		}
	})

	t.Run("non_root_owned_binary", func(t *testing.T) {
		// Create a temporary executable owned by the current user
		tmpDir := t.TempDir()
		tmpScript := filepath.Join(tmpDir, "test_script")

		// Create a simple shell script
		content := []byte("#!/bin/sh\necho test\n")
		if err := os.WriteFile(tmpScript, content, 0755); err != nil {
			t.Fatalf("Failed to create test script: %v", err)
		}

		// Try to execute it - should fail because it's not owned by root
		_, err := SecureExecCommand(tmpScript)
		if err == nil {
			t.Error("Expected error for non-root owned binary")
		}
		if !strings.Contains(err.Error(), "not owned by root") {
			t.Errorf("Expected 'not owned by root' error, got: %v", err)
		}
	})
}
