package handlers

import (
	"html"
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/treytartt/honeydue-api/internal/admin/dto"
)

func TestAdminSortBy_ValidColumn_Works(t *testing.T) {
	tests := []struct {
		name       string
		sortBy     string
		allowlist  []string
		defaultCol string
		expected   string
	}{
		{
			name:       "exact match returns column",
			sortBy:     "created_at",
			allowlist:  []string{"id", "created_at", "updated_at", "name"},
			defaultCol: "created_at",
			expected:   "created_at",
		},
		{
			name:       "case insensitive match returns canonical column",
			sortBy:     "Created_At",
			allowlist:  []string{"id", "created_at", "updated_at", "name"},
			defaultCol: "created_at",
			expected:   "created_at",
		},
		{
			name:       "different valid column",
			sortBy:     "name",
			allowlist:  []string{"id", "created_at", "updated_at", "name"},
			defaultCol: "created_at",
			expected:   "name",
		},
		{
			name:       "date_joined for user handler",
			sortBy:     "date_joined",
			allowlist:  []string{"id", "username", "email", "date_joined", "last_login", "is_active"},
			defaultCol: "date_joined",
			expected:   "date_joined",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			p := dto.PaginationParams{SortBy: tt.sortBy}
			result := p.GetSafeSortBy(tt.allowlist, tt.defaultCol)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestAdminSortBy_SQLInjection_ReturnsDefault(t *testing.T) {
	allowlist := []string{"id", "created_at", "updated_at", "name"}
	defaultCol := "created_at"

	tests := []struct {
		name   string
		sortBy string
	}{
		{
			name:   "SQL injection with DROP TABLE",
			sortBy: "created_at; DROP TABLE users; --",
		},
		{
			name:   "SQL injection with UNION SELECT",
			sortBy: "id UNION SELECT password FROM auth_user",
		},
		{
			name:   "SQL injection with subquery",
			sortBy: "(SELECT password FROM auth_user LIMIT 1)",
		},
		{
			name:   "SQL injection with comment",
			sortBy: "created_at--",
		},
		{
			name:   "SQL injection with semicolon",
			sortBy: "created_at;",
		},
		{
			name:   "SQL injection with OR 1=1",
			sortBy: "created_at OR 1=1",
		},
		{
			name:   "column not in allowlist",
			sortBy: "password",
		},
		{
			name:   "SQL injection with single quotes",
			sortBy: "name'; DROP TABLE users; --",
		},
		{
			name:   "SQL injection with double dashes",
			sortBy: "id -- comment",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			p := dto.PaginationParams{SortBy: tt.sortBy}
			result := p.GetSafeSortBy(allowlist, defaultCol)
			assert.Equal(t, defaultCol, result, "SQL injection attempt should return default column")
		})
	}
}

func TestAdminSortBy_EmptyString_ReturnsDefault(t *testing.T) {
	tests := []struct {
		name       string
		sortBy     string
		defaultCol string
	}{
		{
			name:       "empty string returns default",
			sortBy:     "",
			defaultCol: "created_at",
		},
		{
			name:       "whitespace only returns default",
			sortBy:     "   ",
			defaultCol: "created_at",
		},
		{
			name:       "tab only returns default",
			sortBy:     "\t",
			defaultCol: "date_joined",
		},
		{
			name:       "different default column",
			sortBy:     "",
			defaultCol: "completed_at",
		},
	}

	allowlist := []string{"id", "created_at", "updated_at", "name"}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			p := dto.PaginationParams{SortBy: tt.sortBy}
			result := p.GetSafeSortBy(allowlist, tt.defaultCol)
			assert.Equal(t, tt.defaultCol, result)
		})
	}
}

func TestSendEmail_XSSEscaped(t *testing.T) {
	// SEC-22: Subject and Body must be HTML-escaped before insertion into email template.
	// This tests the html.EscapeString behavior that the handler relies on.
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "script tag in subject",
			input:    `<script>alert("xss")</script>`,
			expected: `&lt;script&gt;alert(&#34;xss&#34;)&lt;/script&gt;`,
		},
		{
			name:     "img onerror payload",
			input:    `<img src=x onerror=alert(1)>`,
			expected: `&lt;img src=x onerror=alert(1)&gt;`,
		},
		{
			name:     "ampersand and angle brackets",
			input:    `Tom & Jerry <bros>`,
			expected: `Tom &amp; Jerry &lt;bros&gt;`,
		},
		{
			name:     "plain text passes through",
			input:    "Hello World",
			expected: "Hello World",
		},
		{
			name:     "single quotes",
			input:    `It's a 'test'`,
			expected: `It&#39;s a &#39;test&#39;`,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			escaped := html.EscapeString(tt.input)
			assert.Equal(t, tt.expected, escaped)
			// Verify the escaped output does NOT contain raw angle brackets from the input
			if tt.input != tt.expected {
				assert.NotContains(t, escaped, "<script>")
				assert.NotContains(t, escaped, "<img")
			}
		})
	}
}
