package api

import (
	"errors"
	"strings"
	"testing"

	"github.com/rcourtman/pulse-go-rewrite/internal/config"
)

func TestSanitizeInstallerURL(t *testing.T) {
	tests := []struct {
		name    string
		input   string
		want    string
		wantErr bool
		errMsg  string
	}{
		// Valid cases
		{
			name:    "empty string returns empty",
			input:   "",
			want:    "",
			wantErr: false,
		},
		{
			name:    "whitespace-only returns empty",
			input:   "   \t  ",
			want:    "",
			wantErr: false,
		},
		{
			name:    "valid http URL",
			input:   "http://example.com",
			want:    "http://example.com",
			wantErr: false,
		},
		{
			name:    "valid https URL",
			input:   "https://example.com",
			want:    "https://example.com",
			wantErr: false,
		},
		{
			name:    "URL with path",
			input:   "https://example.com/path/to/installer",
			want:    "https://example.com/path/to/installer",
			wantErr: false,
		},
		{
			name:    "URL with port",
			input:   "https://example.com:8080",
			want:    "https://example.com:8080",
			wantErr: false,
		},
		{
			name:    "URL with query params preserves them",
			input:   "https://example.com/installer?version=1.0&arch=amd64",
			want:    "https://example.com/installer?version=1.0&arch=amd64",
			wantErr: false,
		},
		{
			name:    "URL with fragment strips fragment",
			input:   "https://example.com/installer#section",
			want:    "https://example.com/installer",
			wantErr: false,
		},
		{
			name:    "URL with fragment and query preserves query, strips fragment",
			input:   "https://example.com/installer?version=1.0#section",
			want:    "https://example.com/installer?version=1.0",
			wantErr: false,
		},
		{
			name:    "URL with leading/trailing whitespace is trimmed",
			input:   "  https://example.com/installer  ",
			want:    "https://example.com/installer",
			wantErr: false,
		},
		{
			name:    "URL with authentication",
			input:   "https://user:pass@example.com/installer",
			want:    "https://user:pass@example.com/installer",
			wantErr: false,
		},

		// Error cases
		{
			name:    "carriage return in URL",
			input:   "https://example.com\r/installer",
			wantErr: true,
			errMsg:  "control characters",
		},
		{
			name:    "newline in URL",
			input:   "https://example.com\n/installer",
			wantErr: true,
			errMsg:  "control characters",
		},
		{
			name:    "both CR and LF in URL",
			input:   "https://example.com\r\n/installer",
			wantErr: true,
			errMsg:  "control characters",
		},
		{
			name:    "ftp scheme rejected",
			input:   "ftp://example.com/installer",
			wantErr: true,
			errMsg:  "scheme must be http or https",
		},
		{
			name:    "file scheme rejected",
			input:   "file:///etc/passwd",
			wantErr: true,
			errMsg:  "scheme must be http or https",
		},
		{
			name:    "javascript scheme rejected",
			input:   "javascript:alert(1)",
			wantErr: true,
			errMsg:  "scheme must be http or https",
		},
		{
			name:    "data scheme rejected",
			input:   "data:text/plain,hello",
			wantErr: true,
			errMsg:  "scheme must be http or https",
		},
		{
			name:    "URL without scheme",
			input:   "example.com/installer",
			wantErr: true,
			errMsg:  "scheme must be http or https",
		},
		{
			name:    "URL without host",
			input:   "https:///path",
			wantErr: true,
			errMsg:  "host component is required",
		},
		{
			name:    "relative URL without host",
			input:   "/path/to/installer",
			wantErr: true,
			errMsg:  "scheme must be http or https",
		},
		{
			name:    "malformed URL",
			input:   "https://[invalid",
			wantErr: true,
			errMsg:  "failed to parse URL",
		},
		{
			name:    "URL with only scheme",
			input:   "https://",
			wantErr: true,
			errMsg:  "host component is required",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := sanitizeInstallerURL(tt.input)
			if tt.wantErr {
				if err == nil {
					t.Errorf("sanitizeInstallerURL() expected error, got nil")
					return
				}
				if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) {
					t.Errorf("sanitizeInstallerURL() error = %q, want substring %q", err.Error(), tt.errMsg)
				}
			} else {
				if err != nil {
					t.Errorf("sanitizeInstallerURL() unexpected error = %v", err)
					return
				}
				if got != tt.want {
					t.Errorf("sanitizeInstallerURL() = %q, want %q", got, tt.want)
				}
			}
		})
	}
}

func TestSanitizeSetupAuthToken(t *testing.T) {
	tests := []struct {
		name    string
		input   string
		want    string
		wantErr bool
		errMsg  string
	}{
		// Valid cases
		{
			name:    "empty string returns empty",
			input:   "",
			want:    "",
			wantErr: false,
		},
		{
			name:    "whitespace-only returns empty",
			input:   "   \t  ",
			want:    "",
			wantErr: false,
		},
		{
			name:    "valid 32-char hex token lowercase",
			input:   "0123456789abcdef0123456789abcdef",
			want:    "0123456789abcdef0123456789abcdef",
			wantErr: false,
		},
		{
			name:    "valid 32-char hex token uppercase",
			input:   "0123456789ABCDEF0123456789ABCDEF",
			want:    "0123456789ABCDEF0123456789ABCDEF",
			wantErr: false,
		},
		{
			name:    "valid 32-char hex token mixed case",
			input:   "0123456789aBcDeF0123456789AbCdEf",
			want:    "0123456789aBcDeF0123456789AbCdEf",
			wantErr: false,
		},
		{
			name:    "valid 64-char hex token",
			input:   "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
			want:    "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
			wantErr: false,
		},
		{
			name:    "valid 128-char hex token (max length)",
			input:   "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
			want:    "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
			wantErr: false,
		},
		{
			name:    "valid hex token with leading/trailing whitespace is trimmed",
			input:   "  0123456789abcdef0123456789abcdef  ",
			want:    "0123456789abcdef0123456789abcdef",
			wantErr: false,
		},

		// Error cases
		{
			name:    "carriage return in token",
			input:   "0123456789abcdef\r0123456789abcdef",
			wantErr: true,
			errMsg:  "control characters",
		},
		{
			name:    "newline in token",
			input:   "0123456789abcdef\n0123456789abcdef",
			wantErr: true,
			errMsg:  "control characters",
		},
		{
			name:    "both CR and LF in token",
			input:   "0123456789abcdef\r\n123456789abcdef",
			wantErr: true,
			errMsg:  "control characters",
		},
		{
			name:    "non-hex character g",
			input:   "0123456789abcdefg123456789abcdef",
			wantErr: true,
			errMsg:  "hexadecimal",
		},
		{
			name:    "non-hex character z",
			input:   "0123456789abcdefz123456789abcdef",
			wantErr: true,
			errMsg:  "hexadecimal",
		},
		{
			name:    "token with spaces",
			input:   "0123456789abcdef 0123456789abcdef",
			wantErr: true,
			errMsg:  "hexadecimal",
		},
		{
			name:    "token with dash",
			input:   "0123456789abcdef-0123456789abcdef",
			wantErr: true,
			errMsg:  "hexadecimal",
		},
		{
			name:    "token too short (31 chars)",
			input:   "0123456789abcdef0123456789abcde",
			wantErr: true,
			errMsg:  "hexadecimal",
		},
		{
			name:    "token too long (129 chars)",
			input:   "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0",
			wantErr: true,
			errMsg:  "hexadecimal",
		},
		{
			name:    "special characters",
			input:   "0123456789abcdef!@#$%^&*()abcdef",
			wantErr: true,
			errMsg:  "hexadecimal",
		},
		{
			name:    "empty hex string (just numbers)",
			input:   "12345678901234567890123456789012",
			want:    "12345678901234567890123456789012",
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := sanitizeSetupAuthToken(tt.input)
			if tt.wantErr {
				if err == nil {
					t.Errorf("sanitizeSetupAuthToken() expected error, got nil")
					return
				}
				if tt.errMsg != "" && !strings.Contains(err.Error(), tt.errMsg) {
					t.Errorf("sanitizeSetupAuthToken() error = %q, want substring %q", err.Error(), tt.errMsg)
				}
			} else {
				if err != nil {
					t.Errorf("sanitizeSetupAuthToken() unexpected error = %v", err)
					return
				}
				if got != tt.want {
					t.Errorf("sanitizeSetupAuthToken() = %q, want %q", got, tt.want)
				}
			}
		})
	}
}

func TestSanitizeErrorMessage(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name      string
		err       error
		operation string
		expected  string
	}{
		{
			name:      "create_client operation",
			err:       errors.New("connection refused"),
			operation: "create_client",
			expected:  "Failed to initialize connection",
		},
		{
			name:      "connection operation",
			err:       errors.New("network unreachable"),
			operation: "connection",
			expected:  "Connection failed. Please check your credentials and network settings",
		},
		{
			name:      "validation operation",
			err:       errors.New("invalid field"),
			operation: "validation",
			expected:  "Invalid configuration",
		},
		{
			name:      "unknown operation",
			err:       errors.New("something went wrong"),
			operation: "unknown_operation",
			expected:  "Operation failed",
		},
		{
			name:      "empty operation string",
			err:       errors.New("error"),
			operation: "",
			expected:  "Operation failed",
		},
		{
			name:      "nil error still returns message",
			err:       nil,
			operation: "create_client",
			expected:  "Failed to initialize connection",
		},
		{
			name:      "detailed error hidden from response",
			err:       errors.New("x509: certificate signed by unknown authority"),
			operation: "connection",
			expected:  "Connection failed. Please check your credentials and network settings",
		},
		{
			name:      "sensitive error hidden from response",
			err:       errors.New("password: secret123"),
			operation: "validation",
			expected:  "Invalid configuration",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			result := sanitizeErrorMessage(tt.err, tt.operation)
			if result != tt.expected {
				t.Errorf("sanitizeErrorMessage() = %q, want %q", result, tt.expected)
			}
		})
	}
}

func TestFindExistingGuestURL(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name      string
		nodeName  string
		endpoints []config.ClusterEndpoint
		expected  string
	}{
		{
			name:      "empty endpoints",
			nodeName:  "node1",
			endpoints: []config.ClusterEndpoint{},
			expected:  "",
		},
		{
			name:      "nil endpoints",
			nodeName:  "node1",
			endpoints: nil,
			expected:  "",
		},
		{
			name:     "exact match",
			nodeName: "pve1",
			endpoints: []config.ClusterEndpoint{
				{NodeName: "pve1", GuestURL: "https://pve1.local:8006"},
				{NodeName: "pve2", GuestURL: "https://pve2.local:8006"},
			},
			expected: "https://pve1.local:8006",
		},
		{
			name:     "match second node",
			nodeName: "pve2",
			endpoints: []config.ClusterEndpoint{
				{NodeName: "pve1", GuestURL: "https://pve1.local:8006"},
				{NodeName: "pve2", GuestURL: "https://pve2.local:8006"},
			},
			expected: "https://pve2.local:8006",
		},
		{
			name:     "no match",
			nodeName: "pve3",
			endpoints: []config.ClusterEndpoint{
				{NodeName: "pve1", GuestURL: "https://pve1.local:8006"},
				{NodeName: "pve2", GuestURL: "https://pve2.local:8006"},
			},
			expected: "",
		},
		{
			name:     "empty node name",
			nodeName: "",
			endpoints: []config.ClusterEndpoint{
				{NodeName: "pve1", GuestURL: "https://pve1.local:8006"},
			},
			expected: "",
		},
		{
			name:     "case sensitive match",
			nodeName: "PVE1",
			endpoints: []config.ClusterEndpoint{
				{NodeName: "pve1", GuestURL: "https://pve1.local:8006"},
			},
			expected: "",
		},
		{
			name:     "returns first match when duplicates exist",
			nodeName: "pve1",
			endpoints: []config.ClusterEndpoint{
				{NodeName: "pve1", GuestURL: "https://first.local:8006"},
				{NodeName: "pve1", GuestURL: "https://second.local:8006"},
			},
			expected: "https://first.local:8006",
		},
		{
			name:     "empty GuestURL returned when matched",
			nodeName: "pve1",
			endpoints: []config.ClusterEndpoint{
				{NodeName: "pve1", GuestURL: ""},
			},
			expected: "",
		},
		{
			name:     "single endpoint match",
			nodeName: "pve1",
			endpoints: []config.ClusterEndpoint{
				{NodeName: "pve1", GuestURL: "https://pve1.local:8006"},
			},
			expected: "https://pve1.local:8006",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			result := findExistingGuestURL(tt.nodeName, tt.endpoints)
			if result != tt.expected {
				t.Errorf("findExistingGuestURL(%q, endpoints) = %q, want %q", tt.nodeName, result, tt.expected)
			}
		})
	}
}
