package cdccluster

import (
	"testing"

	"github.com/stretchr/testify/require"
	"sigs.k8s.io/yaml"
)

func TestSetTomlConfig_SetConfigField_v2(t *testing.T) {
	// Test SetTomlConfig
	provider := NewV2ClusterProvider("", nil, nil)
	provider.Init("ns1", "cdc1")
	cfgInit := `
addr = "0.0.0.0:8300"
log-level = "info"
[sorter]
sort-dir = "/tmp/just_a_test"
cache-size-in-mb = 8
`
	cfgAfterSet := `addr = "127.0.0.1:8300"
advertise-addr = "cdc1.ns1:8300"
log-level = "info"

[log]
  [log.file]
    max-size = 100

[sorter]
  cache-size-in-mb = 8
  sort-dir = "/tmp/just_a_test2"
`
	err := provider.SetTomlConfig(cfgInit)
	require.NoError(t, err)

	require.NoError(t, provider.SetConfigField("addr", "127.0.0.1:8300"))
	require.NoError(t, provider.SetConfigField("advertise-addr", "cdc1.ns1:8300"))
	require.NoError(t, provider.SetConfigField("sorter.sort-dir", "/tmp/just_a_test2"))
	require.NoError(t, provider.SetConfigField("log.file.max-size", 100))

	require.Equal(t, cfgAfterSet, string(provider.(*v2Cluster).desired.Spec.Template.Spec.Config))
}

func TestSetDesired_v2(t *testing.T) {
	provider := NewV2ClusterProvider("tidbclouddev.azurecr.io/tidbcloud/tidb-operator-v2-prestop-checker:prestop-checker-250226", []string{"zone-1", "zone-2", "zone-3"}, map[string]string{
		"tags.tidbcloud.com/shared-pool": "test-pool",
	})
	provider.Init("ns1", "cdc1")
	provider.SetReplicas(3)
	provider.SetVersion("ecr-registery.pingcap/ticdc", "v8.1.0", "master-next-gen")
	provider.SetUpstreamCluster("ns1", "db")
	require.NoError(t, provider.SetTomlConfig(`
log-level = "info"

[log]
  [log.file]
    max-size = 100
`))
	require.NoError(t, provider.SetConfigField("cluster-id", "my-cluster-id"))
	provider.SetNodeSelector(map[string]string{"kubernetes.io/arch": "amd64"})
	provider.SetStorage("100Gi", "vac-1")

	yamlData, err := yaml.Marshal(provider.(*v2Cluster).desired)
	require.NoError(t, err)
	// NOTE: the following YAML may need to be updated after the CRD is updated or even the K8s deps is updated
	require.Equal(t, `metadata:
  creationTimestamp: null
  name: cdc1
  namespace: ns1
spec:
  cluster:
    name: db
  replicas: 3
  schedulePolicies:
  - evenlySpread:
      topologies:
      - topology:
          topology.kubernetes.io/zone: zone-1
      - topology:
          topology.kubernetes.io/zone: zone-2
      - topology:
          topology.kubernetes.io/zone: zone-3
    type: EvenlySpread
  template:
    metadata: {}
    spec:
      config: |
        cluster-id = "my-cluster-id"
        log-level = "info"

        [log]
          [log.file]
            max-size = 100
      image: ecr-registery.pingcap/ticdc:master-next-gen
      overlay:
        pod:
          metadata:
            annotations:
              pingcap.com/ignore-diff: spec.dnsPolicy
            labels:
              app.kubernetes.io/component: ticdc
              app.kubernetes.io/instance: cdc1
              app.kubernetes.io/managed-by: tidb-operator
              app.kubernetes.io/name: ticdc-cluster
              tags.tidbcloud.com/component: ticdc
              tags.tidbcloud.com/resource-pool: test-pool
          spec:
            affinity:
              podAntiAffinity:
                requiredDuringSchedulingIgnoredDuringExecution:
                - labelSelector:
                    matchLabels:
                      app.kubernetes.io/component: ticdc
                  topologyKey: kubernetes.io/hostname
            containers:
            - name: ticdc
              resources: {}
              volumeMounts:
              - mountPath: /var/lib/ticdc
                name: sort-kv
            dnsPolicy: ClusterFirstWithHostNet
            hostNetwork: true
            nodeSelector:
              kubernetes.io/arch: amd64
            priorityClassName: tidb-ng-component-critical
            securityContext: {}
            tolerations:
            - effect: NoSchedule
              key: dedicated
              operator: Equal
              value: worker
            volumes:
            - ephemeral:
                volumeClaimTemplate:
                  metadata:
                    creationTimestamp: null
                    labels:
                      tags.tidbcloud.com/component: ticdc
                      tags.tidbcloud.com/resource-pool: test-pool
                  spec:
                    accessModes:
                    - ReadWriteOnce
                    resources:
                      requests:
                        storage: 100Gi
                    storageClassName: cloud-ssd
                    volumeAttributesClassName: vac-1
              name: sort-kv
      preStop:
        image: tidbclouddev.azurecr.io/tidbcloud/tidb-operator-v2-prestop-checker:prestop-checker-250226
      resources: {}
      server:
        ports: {}
      updateStrategy: {}
      version: v8.1.0
status:
  currentReplicas: 0
  readyReplicas: 0
  replicas: 0
  selector: ""
  updatedReplicas: 0
`, string(yamlData))
}

func TestParseNodeSpecLabelValue(t *testing.T) {
	// testCases is a slice of structs, where each struct defines a single test case.
	testCases := []struct {
		name    string // A descriptive name for the test case.
		input   string // The input string for the function.
		wantCPU uint32 // The expected CPU value after parsing.
		wantMem uint32 // The expected Memory value after parsing.
		wantErr bool   // A boolean indicating if an error is expected.
	}{
		// 1. Valid cases (the "happy path")
		{
			name:    "Standard case with multi-digit numbers",
			input:   "8C16G",
			wantCPU: 8,
			wantMem: 16,
			wantErr: false,
		},
		{
			name:    "Single digit numbers",
			input:   "1C2G",
			wantCPU: 1,
			wantMem: 2,
			wantErr: false,
		},
		{
			name:    "Large numbers",
			input:   "128C512G",
			wantCPU: 128,
			wantMem: 512,
			wantErr: false,
		},
		{
			name:    "Value with zero CPU",
			input:   "0C64G",
			wantCPU: 0,
			wantMem: 64,
			wantErr: false,
		},
		{
			name:    "Value with zero Memory",
			input:   "16C0G",
			wantCPU: 16,
			wantMem: 0,
			wantErr: false,
		},

		// 2. Invalid and edge cases
		{
			name:    "Empty string input",
			input:   "",
			wantErr: true,
		},
		{
			name:    "Lowercase c separator",
			input:   "8c16G",
			wantErr: true,
		},
		{
			name:    "Lowercase g suffix",
			input:   "8C16g",
			wantErr: true,
		},
		{
			name:    "Wrong separator character",
			input:   "8-16G",
			wantErr: true,
		},
		{
			name:    "Missing G suffix",
			input:   "8C16",
			wantErr: true,
		},
		{
			name:    "Missing C separator",
			input:   "816G",
			wantErr: true,
		},
		{
			name:    "Missing CPU part",
			input:   "C16G",
			wantErr: true,
		},
		{
			name:    "Missing Memory part",
			input:   "8C",
			wantErr: true,
		},
		{
			name:    "Non-numeric CPU",
			input:   "aC16G",
			wantErr: true,
		},
		{
			name:    "Non-numeric Memory",
			input:   "8CxyzG",
			wantErr: true,
		},
		{
			name:    "Extra characters at the end",
			input:   "8C16G-extra",
			wantCPU: 8,
			wantMem: 16,
			wantErr: false,
		},
		{
			name:    "Extra characters at the start",
			input:   "extra-8C16G",
			wantErr: true,
		},
		{
			name:    "Leading and trailing spaces",
			input:   " 8C16G ",
			wantCPU: 8,
			wantMem: 16,
			wantErr: false,
		},
	}

	// Iterate over all defined test cases.
	for _, tc := range testCases {
		// t.Run creates a sub-test for each case, making test output easier to read.
		t.Run(tc.name, func(t *testing.T) {
			gotCPU, gotMem, err := ParseNodeSpecLabelValue(tc.input)

			// Check if the error status matches the expectation.
			if (err != nil) != tc.wantErr {
				// t.Fatalf logs the error and stops the execution of this specific sub-test.
				t.Fatalf("ParseNodeSpecLabelValue(%q) returned error: %v, wantErr: %v", tc.input, err, tc.wantErr)
			}

			// If no error was expected, we must also check if the returned values are correct.
			if !tc.wantErr {
				if gotCPU != tc.wantCPU {
					t.Errorf("ParseNodeSpecLabelValue(%q) got CPU = %d; want %d", tc.input, gotCPU, tc.wantCPU)
				}
				if gotMem != tc.wantMem {
					t.Errorf("ParseNodeSpecLabelValue(%q) got Memory = %d; want %d", tc.input, gotMem, tc.wantMem)
				}
			}
		})
	}
}
