package api

import (
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

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

	tests := []struct {
		name     string
		uid      string
		expected *uint64
		wantErr  string
	}{
		{
			name:     "empty uid returns nil",
			uid:      "",
			expected: nil,
		},
		{
			name:     "valid numeric uid",
			uid:      "1000",
			expected: new(uint64(1000)),
		},
		{
			name:     "zero uid",
			uid:      "0",
			expected: new(uint64(0)),
		},
		{
			name:    "invalid non-numeric uid",
			uid:     "root",
			wantErr: "invalid Uid 'root'",
		},
		{
			name:    "negative uid",
			uid:     "-1",
			wantErr: "invalid Uid",
		},
		{
			name:    "very large uid",
			uid:     "18446744073709551615", // max uint64
			wantErr: "invalid Uid '18446744073709551615': value too high",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			mount := &ConfigMount{Uid: tt.uid}
			uid, err := mount.GetNumericUid()

			if tt.wantErr != "" {
				require.Error(t, err)
				assert.Contains(t, err.Error(), tt.wantErr)
				assert.Nil(t, uid)
				return
			}

			require.NoError(t, err)
			if tt.expected == nil {
				assert.Nil(t, uid)
			} else {
				require.NotNil(t, uid)
				assert.Equal(t, *tt.expected, *uid)
			}
		})
	}
}

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

	tests := []struct {
		name     string
		gid      string
		expected *uint64
		wantErr  string
	}{
		{
			name:     "empty gid returns nil",
			gid:      "",
			expected: nil,
		},
		{
			name:     "valid numeric gid",
			gid:      "1000",
			expected: new(uint64(1000)),
		},
		{
			name:     "zero gid",
			gid:      "0",
			expected: new(uint64(0)),
		},
		{
			name:    "invalid non-numeric gid",
			gid:     "wheel",
			wantErr: "invalid Gid 'wheel'",
		},
		{
			name:    "negative gid",
			gid:     "-1",
			wantErr: "invalid Gid",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			mount := &ConfigMount{Gid: tt.gid}
			gid, err := mount.GetNumericGid()

			if tt.wantErr != "" {
				require.Error(t, err)
				assert.Contains(t, err.Error(), tt.wantErr)
				assert.Nil(t, gid)
				return
			}

			require.NoError(t, err)
			if tt.expected == nil {
				assert.Nil(t, gid)
			} else {
				require.NotNil(t, gid)
				assert.Equal(t, *tt.expected, *gid)
			}
		})
	}
}

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

	mode := os.FileMode(0o644)

	tests := []struct {
		name    string
		configs []ConfigSpec
		mounts  []ConfigMount
		wantErr string
	}{
		{
			name:    "empty configs and mounts",
			configs: []ConfigSpec{},
			mounts:  []ConfigMount{},
		},
		{
			name: "valid configs without mounts",
			configs: []ConfigSpec{
				{Name: "config1", Content: []byte("content1")},
				{Name: "config2", Content: []byte("content2")},
			},
			mounts: []ConfigMount{},
		},
		{
			name: "valid configs with valid mounts",
			configs: []ConfigSpec{
				{Name: "config1", Content: []byte("content1")},
				{Name: "config2", Content: []byte("content2")},
			},
			mounts: []ConfigMount{
				{ConfigName: "config1", ContainerPath: "/etc/config1"},
				{ConfigName: "config2", ContainerPath: "/etc/config2", Uid: "1000", Gid: "1000"},
			},
		},
		{
			name: "config with empty name",
			configs: []ConfigSpec{
				{Name: "", Content: []byte("content")},
			},
			mounts:  []ConfigMount{},
			wantErr: "config name is required",
		},
		{
			name: "duplicate config names",
			configs: []ConfigSpec{
				{Name: "config1", Content: []byte("content1")},
				{Name: "config1", Content: []byte("content2")},
			},
			mounts:  []ConfigMount{},
			wantErr: "duplicate config name: 'config1'",
		},
		{
			name: "mount with empty config name",
			configs: []ConfigSpec{
				{Name: "config1", Content: []byte("content1")},
			},
			mounts: []ConfigMount{
				{ConfigName: "", ContainerPath: "/etc/config"},
			},
			wantErr: "config mount source is required",
		},
		{
			name: "mount referencing non-existent config",
			configs: []ConfigSpec{
				{Name: "config1", Content: []byte("content1")},
			},
			mounts: []ConfigMount{
				{ConfigName: "nonexistent", ContainerPath: "/etc/config"},
			},
			wantErr: "config mount source 'nonexistent' does not refer to any defined config",
		},
		{
			name: "mount with invalid uid",
			configs: []ConfigSpec{
				{Name: "config1", Content: []byte("content1")},
			},
			mounts: []ConfigMount{
				{ConfigName: "config1", ContainerPath: "/etc/config", Uid: "invalid"},
			},
			wantErr: "invalid Uid 'invalid'",
		},
		{
			name: "mount with invalid gid",
			configs: []ConfigSpec{
				{Name: "config1", Content: []byte("content1")},
			},
			mounts: []ConfigMount{
				{ConfigName: "config1", ContainerPath: "/etc/config", Gid: "invalid"},
			},
			wantErr: "invalid Gid 'invalid'",
		},
		{
			name: "mount with relative container path",
			configs: []ConfigSpec{
				{Name: "config1", Content: []byte("content1")},
			},
			mounts: []ConfigMount{
				{ConfigName: "config1", ContainerPath: "relative/path"},
			},
			wantErr: "container path must be absolute",
		},
		{
			name: "mount with empty container path",
			configs: []ConfigSpec{
				{Name: "config1", Content: []byte("content1")},
			},
			mounts: []ConfigMount{
				{ConfigName: "config1", ContainerPath: ""},
			},
			// Empty path is allowed
		},
		{
			name: "mount with absolute container path",
			configs: []ConfigSpec{
				{Name: "config1", Content: []byte("content1")},
			},
			mounts: []ConfigMount{
				{ConfigName: "config1", ContainerPath: "/absolute/path"},
			},
		},
		{
			name: "complex valid scenario",
			configs: []ConfigSpec{
				{Name: "nginx-conf", Content: []byte("server { listen 80; }")},
				{Name: "app-config", Content: []byte("debug=true")},
				{Name: "cert", Content: []byte("-----BEGIN CERTIFICATE-----")},
			},
			mounts: []ConfigMount{
				{ConfigName: "nginx-conf", ContainerPath: "/etc/nginx/nginx.conf", Uid: "0", Gid: "0", Mode: &mode},
				{ConfigName: "app-config", ContainerPath: "/app/config.env"},
				{ConfigName: "cert", ContainerPath: "/etc/ssl/cert.pem", Uid: "1000", Gid: "1000"},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()

			err := ValidateConfigsAndMounts(tt.configs, tt.mounts)

			if tt.wantErr != "" {
				require.Error(t, err)
				assert.Contains(t, err.Error(), tt.wantErr)
				return
			}

			require.NoError(t, err)
		})
	}
}
