package helpers

import (
	"testing"

	"github.com/jfraeys/fetch_ml/internal/api/helpers"
	"github.com/jfraeys/fetch_ml/internal/queue"
)

func TestNewTaskErrorMapper(t *testing.T) {
	mapper := helpers.NewTaskErrorMapper()
	if mapper == nil {
		t.Error("NewTaskErrorMapper() returned nil")
	}
}

func TestTaskErrorMapper_MapJupyterError(t *testing.T) {
	mapper := helpers.NewTaskErrorMapper()

	tests := []struct {
		name string
		task *queue.Task
		want helpers.ErrorCode
	}{
		{
			name: "nil task",
			task: nil,
			want: 0x00, // ErrorCodeUnknownError
		},
		{
			name: "cancelled task",
			task: &queue.Task{Status: "cancelled"},
			want: 0x24, // ErrorCodeJobCancelled
		},
		{
			name: "oom error",
			task: &queue.Task{Status: "failed", Error: "out of memory"},
			want: 0x30, // ErrorCodeOutOfMemory
		},
		{
			name: "oom shorthand",
			task: &queue.Task{Status: "failed", Error: "OOM killed"},
			want: 0x30, // ErrorCodeOutOfMemory
		},
		{
			name: "disk full",
			task: &queue.Task{Status: "failed", Error: "no space left on device"},
			want: 0x31, // ErrorCodeDiskFull
		},
		{
			name: "disk full alt",
			task: &queue.Task{Status: "failed", Error: "disk full"},
			want: 0x31, // ErrorCodeDiskFull
		},
		{
			name: "rate limit",
			task: &queue.Task{Status: "failed", Error: "rate limit exceeded"},
			want: 0x33, // ErrorCodeServiceUnavailable
		},
		{
			name: "throttle",
			task: &queue.Task{Status: "failed", Error: "request throttled"},
			want: 0x33, // ErrorCodeServiceUnavailable
		},
		{
			name: "timeout",
			task: &queue.Task{Status: "failed", Error: "timed out waiting"},
			want: 0x14, // ErrorCodeTimeout
		},
		{
			name: "deadline",
			task: &queue.Task{Status: "failed", Error: "context deadline exceeded"},
			want: 0x14, // ErrorCodeTimeout
		},
		{
			name: "connection refused",
			task: &queue.Task{Status: "failed", Error: "connection refused"},
			want: 0x12, // ErrorCodeNetworkError
		},
		{
			name: "connection reset",
			task: &queue.Task{Status: "failed", Error: "connection reset by peer"},
			want: 0x12, // ErrorCodeNetworkError
		},
		{
			name: "network unreachable",
			task: &queue.Task{Status: "failed", Error: "network unreachable"},
			want: 0x12, // ErrorCodeNetworkError
		},
		{
			name: "queue not configured",
			task: &queue.Task{Status: "failed", Error: "queue not configured"},
			want: 0x32, // ErrorCodeInvalidConfiguration
		},
		{
			name: "generic failed",
			task: &queue.Task{Status: "failed", Error: "something went wrong"},
			want: 0x23, // ErrorCodeJobExecutionFailed
		},
		{
			name: "unknown status",
			task: &queue.Task{Status: "unknown", Error: "unknown error"},
			want: 0x00, // ErrorCodeUnknownError
		},
		{
			name: "case insensitive - cancelled",
			task: &queue.Task{Status: "CANCELLED"},
			want: 0x24, // ErrorCodeJobCancelled
		},
		{
			name: "case insensitive - oom",
			task: &queue.Task{Status: "FAILED", Error: "OUT OF MEMORY"},
			want: 0x30, // ErrorCodeOutOfMemory
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := mapper.MapJupyterError(tt.task)
			if got != tt.want {
				t.Errorf("MapJupyterError() = 0x%02X, want 0x%02X", got, tt.want)
			}
		})
	}
}

func TestTaskErrorMapper_MapError(t *testing.T) {
	mapper := helpers.NewTaskErrorMapper()

	tests := []struct {
		name        string
		task        *queue.Task
		defaultCode helpers.ErrorCode
		want        helpers.ErrorCode
	}{
		{
			name:        "nil task returns default",
			task:        nil,
			defaultCode: 0x11, // ErrorCodeDatabaseError
			want:        0x11,
		},
		{
			name:        "cancelled",
			task:        &queue.Task{Status: "cancelled"},
			defaultCode: 0x00,
			want:        0x24, // ErrorCodeJobCancelled
		},
		{
			name:        "oom",
			task:        &queue.Task{Status: "failed", Error: "oom"},
			defaultCode: 0x00,
			want:        0x30, // ErrorCodeOutOfMemory
		},
		{
			name:        "timeout",
			task:        &queue.Task{Status: "failed", Error: "timeout"},
			defaultCode: 0x00,
			want:        0x14, // ErrorCodeTimeout
		},
		{
			name:        "generic failed",
			task:        &queue.Task{Status: "failed", Error: "generic error"},
			defaultCode: 0x00,
			want:        0x23, // ErrorCodeJobExecutionFailed
		},
		{
			name:        "unknown status returns default",
			task:        &queue.Task{Status: "weird", Error: "unknown"},
			defaultCode: 0x01,
			want:        0x01,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := mapper.MapError(tt.task, tt.defaultCode)
			if got != tt.want {
				t.Errorf("MapError() = 0x%02X, want 0x%02X", got, tt.want)
			}
		})
	}
}

func TestParseResourceRequest(t *testing.T) {
	tests := []struct {
		name    string
		payload []byte
		want    *helpers.ResourceRequest
		wantErr bool
	}{
		{
			name:    "empty payload",
			payload: []byte{},
			want:    nil,
			wantErr: false,
		},
		{
			name:    "nil payload",
			payload: nil,
			want:    nil,
			wantErr: false,
		},
		{
			name:    "valid minimal",
			payload: []byte{4, 8, 1, 0},
			want:    &helpers.ResourceRequest{CPU: 4, MemoryGB: 8, GPU: 1, GPUMemory: ""},
			wantErr: false,
		},
		{
			name:    "valid with gpu memory",
			payload: []byte{8, 16, 2, 4, '8', 'G', 'B', '!'},
			want:    &helpers.ResourceRequest{CPU: 8, MemoryGB: 16, GPU: 2, GPUMemory: "8GB!"},
			wantErr: false,
		},
		{
			name:    "too short",
			payload: []byte{1, 2},
			want:    nil,
			wantErr: true,
		},
		{
			name:    "invalid gpu mem length",
			payload: []byte{1, 2, 1, 10, 'a'},
			want:    nil,
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := helpers.ParseResourceRequest(tt.payload)
			if (err != nil) != tt.wantErr {
				t.Errorf("ParseResourceRequest() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			if tt.want == nil {
				if got != nil {
					t.Errorf("ParseResourceRequest() = %v, want nil", got)
				}
			} else if got == nil {
				t.Errorf("ParseResourceRequest() = nil, want %v", tt.want)
			} else {
				if got.CPU != tt.want.CPU {
					t.Errorf("CPU = %d, want %d", got.CPU, tt.want.CPU)
				}
				if got.MemoryGB != tt.want.MemoryGB {
					t.Errorf("MemoryGB = %d, want %d", got.MemoryGB, tt.want.MemoryGB)
				}
				if got.GPU != tt.want.GPU {
					t.Errorf("GPU = %d, want %d", got.GPU, tt.want.GPU)
				}
				if got.GPUMemory != tt.want.GPUMemory {
					t.Errorf("GPUMemory = %q, want %q", got.GPUMemory, tt.want.GPUMemory)
				}
			}
		})
	}
}

func TestMarshalJSONOrEmpty(t *testing.T) {
	tests := []struct {
		name string
		data interface{}
		want []byte
	}{
		{
			name: "simple map",
			data: map[string]string{"key": "value"},
			want: []byte(`{"key":"value"}`),
		},
		{
			name: "string slice",
			data: []string{"a", "b", "c"},
			want: []byte(`["a","b","c"]`),
		},
		{
			name: "empty slice",
			data: []int{},
			want: []byte(`[]`),
		},
		{
			name: "nil",
			data: nil,
			want: []byte(`null`),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := helpers.MarshalJSONOrEmpty(tt.data)
			// For valid JSON, compare strings since JSON formatting might vary
			gotStr := string(got)
			wantStr := string(tt.want)
			if gotStr != wantStr {
				t.Errorf("MarshalJSONOrEmpty() = %q, want %q", gotStr, wantStr)
			}
		})
	}
}

func TestMarshalJSONOrEmpty_ErrorCase(t *testing.T) {
	// Test with a value that can't be marshaled (function)
	got := helpers.MarshalJSONOrEmpty(func() {})
	want := []byte("[]")
	if string(got) != string(want) {
		t.Errorf("MarshalJSONOrEmpty() with invalid data = %q, want %q", string(got), string(want))
	}
}

func TestMarshalJSONBytes(t *testing.T) {
	data := map[string]int{"count": 42}
	got, err := helpers.MarshalJSONBytes(data)
	if err != nil {
		t.Errorf("MarshalJSONBytes() unexpected error: %v", err)
	}
	want := `{"count":42}`
	if string(got) != want {
		t.Errorf("MarshalJSONBytes() = %q, want %q", string(got), want)
	}
}

func TestIsEmptyJSON(t *testing.T) {
	tests := []struct {
		name string
		data []byte
		want bool
	}{
		{"empty", []byte{}, true},
		{"null", []byte("null"), true},
		{"empty array", []byte("[]"), true},
		{"empty object", []byte("{}"), true},
		{"whitespace", []byte("   "), true},
		{"data", []byte(`{"key":"value"}`), false},
		{"non-empty array", []byte("[1,2,3]"), false},
		{"null with spaces", []byte("  null  "), true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := helpers.IsEmptyJSON(tt.data); got != tt.want {
				t.Errorf("IsEmptyJSON() = %v, want %v", got, tt.want)
			}
		})
	}
}
