package app

import (
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/server"
	"github.com/tidbcloud/cluster-service-ng/pkg/clustergateway"
)

func TestDown2UpListLightweightBackupsResponse(t *testing.T) {
	t.Run("converts full backup response correctly", func(t *testing.T) {
		resp := &server.ListLightweightBackupsOutput{
			DfsPrefix: "prefix",
			DfsBucket: "bucket",
			DfsRegion: "region",
			Keyspace2ClusterId: map[string]string{
				"ks1": "c1",
				"ks3": "c3",
			},
			ListLightweightBackupsResponse: clustergateway.ListLightweightBackupsResponse{
				ClusterID: 123,
				Data: []clustergateway.KernelLightweightBackupItem{
					{
						ID:             321,
						Name:           "backup-name-1",
						LastModifyTime: "2025-12-06 12:00:00.000",
						Details: clustergateway.BackupItemDetails{
							BackupTs: 111222333,
							Keyspaces: []clustergateway.KeyspaceBackupInfo{
								{KeyspaceName: "ks1", KeyspaceState: "ENABLED", Size: 1000},
								{KeyspaceName: "DEFAULT", KeyspaceState: "ENABLED", Size: 999},
								{KeyspaceName: "ks2", KeyspaceState: "DISABLED", Size: 888},
								{KeyspaceName: "ks3", KeyspaceState: "ENABLED", Size: 777},
							},
						},
					},
				},
			},
		}

		got := down2UpListLightweightBackupsResponse(resp)

		assert.NotNil(t, got)
		assert.Equal(t, "prefix", got.DfsPrefix)
		assert.Equal(t, "bucket", got.DfsBucket)
		assert.Equal(t, "region", got.DfsRegion)
		assert.Equal(t, uint64(123), got.PdClusterId)
		assert.Equal(t, uint32(1), got.TotalCount)
		assert.Len(t, got.Backups, 1)

		backup := got.Backups[0]
		assert.Equal(t, uint64(321), backup.Id)
		assert.Equal(t, "backup-name-1", backup.Name)
		assert.Equal(t, "2025-12-06 12:00:00.000", backup.BackupTime)
		assert.Equal(t, uint64(111222333), backup.BackupTs)

		// Only ks1 and ks3 should be included
		assert.Len(t, backup.ClusterBackupInfo, 2)
		assert.Equal(t, "c1", backup.ClusterBackupInfo[0].ClusterId)
		assert.Equal(t, uint64(1000), backup.ClusterBackupInfo[0].BackupSizeBytes)
		assert.Equal(t, "c3", backup.ClusterBackupInfo[1].ClusterId)
		assert.Equal(t, uint64(777), backup.ClusterBackupInfo[1].BackupSizeBytes)
	})

	t.Run("empty data returns empty list", func(t *testing.T) {
		resp := &server.ListLightweightBackupsOutput{
			DfsPrefix: "pre",
			DfsBucket: "buck",
			DfsRegion: "reg",
			ListLightweightBackupsResponse: clustergateway.ListLightweightBackupsResponse{
				ClusterID: 123,
				Data:      []clustergateway.KernelLightweightBackupItem{},
			},
		}

		got := down2UpListLightweightBackupsResponse(resp)

		assert.NotNil(t, got)
		assert.Equal(t, uint32(0), got.TotalCount)
		assert.Empty(t, got.Backups)
	})
}

func TestValidateTimeFormat(t *testing.T) {
	tests := []struct {
		name     string
		timestr  string
		layout   string
		expected bool
	}{
		{
			name:     "valid lastBackupTimeFormat time",
			timestr:  "2025-04-30 15:04:05.123",
			layout:   pointInTimeFormat,
			expected: true,
		},
		{
			name:     "invalid lastBackupTimeFormat time",
			timestr:  "2025-04-30 15:04:05",
			layout:   pointInTimeFormat,
			expected: false,
		},
		{
			name:     "empty time string",
			timestr:  "",
			layout:   time.RFC3339,
			expected: false,
		},
		{
			name:     "empty layout",
			timestr:  "2025-04-30",
			layout:   "",
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := validateTimeFormat(tt.timestr, tt.layout)
			if result != tt.expected {
				t.Errorf("validateTimeFormat(%q, %q) = %v, expected %v", tt.timestr, tt.layout, result, tt.expected)
			}
		})
	}
}

func TestValidateCreateLightweightRestoreRequest(t *testing.T) {
	tests := []struct {
		name        string
		req         *apiv1.CreateLightweightRestoreRequest
		wantErr     bool
		expectedMsg string
	}{
		{
			name:        "Invalid restore mode ",
			req:         &apiv1.CreateLightweightRestoreRequest{KernelBackupId: 1, KernelBackupName: "foo", PointInTime: "2025-04-30T12:00:00Z"},
			wantErr:     true,
			expectedMsg: "validate restore mode failed",
		},
		{
			name:    "Valid mode",
			req:     &apiv1.CreateLightweightRestoreRequest{KernelBackupId: 123, KernelBackupName: "snapshot1"},
			wantErr: false,
		},

		{
			name:    "invalid resource",
			req:     &apiv1.CreateLightweightRestoreRequest{KernelBackupId: 123, KernelBackupName: "snapshot1", ResourceCpu: "dummy"},
			wantErr: true,
		},

		{
			name:    "valid resource",
			req:     &apiv1.CreateLightweightRestoreRequest{KernelBackupId: 123, KernelBackupName: "snapshot1", ResourceCpu: "8"},
			wantErr: false,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			err := validateCreateLightweightRestoreRequest(tc.req)
			if (err != nil) != tc.wantErr {
				t.Fatalf("expected error: %v, got: %v", tc.wantErr, err)
			}
			if tc.wantErr {
				require.Error(t, err)
			}
		})
	}
}

func TestValidateRestoreMode(t *testing.T) {
	tests := []struct {
		name        string
		req         *apiv1.CreateLightweightRestoreRequest
		wantErr     bool
		expectedMsg string
	}{
		{
			name:    "Valid snapshot restore",
			req:     &apiv1.CreateLightweightRestoreRequest{KernelBackupId: 123, KernelBackupName: "snapshot-001"},
			wantErr: false,
		},
		{
			name:        "Missing backup_name in snapshot restore",
			req:         &apiv1.CreateLightweightRestoreRequest{KernelBackupId: 123},
			wantErr:     true,
			expectedMsg: "both kernel_backup_id and kernel_backup_name must be set for snapshot restore",
		},
		{
			name:    "Valid PiTR restore",
			req:     &apiv1.CreateLightweightRestoreRequest{PointInTime: "2025-04-30 12:00:00.000"},
			wantErr: false,
		},
		{
			name:        "Invalid point_in_time format",
			req:         &apiv1.CreateLightweightRestoreRequest{PointInTime: "30-04-2025"},
			wantErr:     true,
			expectedMsg: "invalid point_in_time format",
		},
		{
			name:        "Both snapshot and PiTR provided (conflict)",
			req:         &apiv1.CreateLightweightRestoreRequest{KernelBackupId: 123, KernelBackupName: "snapshot-001", PointInTime: "2025-04-30 12:00:00.000"},
			wantErr:     true,
			expectedMsg: "snapshot fields (kernel_backup_id, kernel_backup_name) and point_in_time cannot both be set",
		},
		{
			name:        "Neither snapshot nor PiTR provided",
			req:         &apiv1.CreateLightweightRestoreRequest{},
			wantErr:     true,
			expectedMsg: "either snapshot (kernel_backup_id and kernel_backup_name) or point_in_time must be specified",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			err := validateRestoreMode(tc.req)
			if (err != nil) != tc.wantErr {
				t.Fatalf("expected error = %v, got = %v", tc.wantErr, err)
			}
			if tc.wantErr && !strings.Contains(err.Error(), tc.expectedMsg) {
				t.Errorf("expected error message to contain %q, got %q", tc.expectedMsg, err.Error())
			}
		})
	}
}

func TestValidateRestoreResources(t *testing.T) {
	t.Run("valid resources", func(t *testing.T) {
		req := &apiv1.CreateLightweightRestoreRequest{
			ResourceCpu:     "1000m",
			ResourceMemory:  "1Gi",
			ResourceStorage: "10Gi",
		}
		err := validateRestoreResources(req)
		require.NoError(t, err)
	})

	t.Run("invalid CPU", func(t *testing.T) {
		req := &apiv1.CreateLightweightRestoreRequest{
			ResourceCpu:     "notacpu",
			ResourceMemory:  "1Gi",
			ResourceStorage: "10Gi",
		}
		err := validateRestoreResources(req)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "invalid ResourceCpu format")
	})

	t.Run("invalid Memory", func(t *testing.T) {
		req := &apiv1.CreateLightweightRestoreRequest{
			ResourceCpu:     "500m",
			ResourceMemory:  "1Xx",
			ResourceStorage: "10Gi",
		}
		err := validateRestoreResources(req)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "invalid ResourceMemory format")
	})

	t.Run("invalid Storage", func(t *testing.T) {
		req := &apiv1.CreateLightweightRestoreRequest{
			ResourceCpu:     "500m",
			ResourceMemory:  "512Mi",
			ResourceStorage: "bad",
		}
		err := validateRestoreResources(req)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "invalid ResourceStorage format")
	})

	t.Run("empty fields should pass", func(t *testing.T) {
		req := &apiv1.CreateLightweightRestoreRequest{}
		err := validateRestoreResources(req)
		require.NoError(t, err)
	})
}
