package br

import (
	"context"
	"testing"

	"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/config"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	schemav1 "github.com/tidbcloud/cluster-service-ng/pkg/storage/etcd/schema/v1"
)

func Test_RenderTiBRCR_ForBackup_AWS(t *testing.T) {
	action := &createTiBRAutoBackupAction{
		imageConfig:        initImageConfig4TiBRBackup(),
		bizConfig:          &config.BizConfig{},
		templateVersionSvc: newTestTemplateVersionSvc(t),
	}

	image := "385595570414.dkr.ecr.us-west-2.amazonaws.com/tidbcloud/tikv:dedicated-next-gen"

	defaultConfig := schemav1.NewTomlConfig(map[string]any{})
	defaultConfig.Set("native-br.backup-interval", "60s")
	defaultConfig.Set("native-br.backup-tolerate-err", true)
	cfg, err := defaultConfig.MarshalTOML()
	if err != nil {
		t.Fatalf("failed to marshal default config: %v", err)
	}

	yaml, err := action.renderTiBRCR(context.Background(), initSharedPool4TiBRBackup_AWS(), "60205", image, string(cfg), "1", "4812Mi", testTiBRNodePlacement)
	require.NoError(t, err)
	assert.YAMLEq(t, tibrCRBackup_AWS, yaml)
}

const tibrCRBackup_AWS = `apiVersion: br.pingcap.com/v1alpha1
kind: TiBR
metadata:
  name: tibr-default
  namespace: tidb66e10e9ff65ef479654dde3968d3440d-0-0
spec:
  cluster:
    name: db
  image: 385595570414.dkr.ecr.us-west-2.amazonaws.com/tidbcloud/tikv:dedicated-next-gen
  config: |

    [native-br]
    backup-interval = "60s"
    backup-tolerate-err = true
  resources:
    cpu: 1
    memory: "4812Mi"
  overlay:
    pod:
      metadata:
        labels:
          app.kubernetes.io/instance: db-b598d9e9
          app.kubernetes.io/component: tibr
          tags.tidbcloud.com/tenant: "60205"
          tags.tidbcloud.com/component: tibr_backup
          tags.tidbcloud.com/resource-pool: "66e10e9ff65ef479654dde3968d3440d-0-0"
        annotations:
          prometheus.io/path: /metrics
          prometheus.io/port: "19500"
          prometheus.io/scrape: "true"
      spec:
        nodeSelector:
          node-spec: "4C16G"
          tidbcloud.com/node-pool-k8s-version: "1.33"
        priorityClassName: tidb-ng-component-critical
        serviceAccountName: tidb-cluster
        securityContext:
          runAsUser: 1000
          runAsGroup: 2000
          fsGroup: 2000
          runAsNonRoot: true
        volumes:
        - name: data
          emptyDir: {}
        - name: tmp-volume
          emptyDir: {}
        containers:
          - name: api-server
            env:
            - name: DFS_PREFIX
              value: 6841534A/cse
            - name: DFS_S3_BUCKET
              value: us-east-1-66e10e9ff65ef479654dde3968d3440d-0-0-data
            - name: DFS_S3_REGION
              value: us-east-1
            securityContext:
              readOnlyRootFilesystem: true
              allowPrivilegeEscalation: false
              privileged: false
              capabilities:
                drop:
                  - NET_RAW
            volumeMounts:
            - name: data
              mountPath: /data
            - name: tmp-volume
              mountPath: /tmp
            ports:
            - containerPort: 19500
              hostPort: 19500
              name: server
              protocol: TCP
        tolerations:
        - effect: NoSchedule
          key: dedicated
          operator: Equal
          value: worker
`

func Test_RenderTiBRCR_ForBackup_ALICLOUD(t *testing.T) {
	action := &createTiBRAutoBackupAction{
		imageConfig:        initImageConfig4TiBRBackup(),
		bizConfig:          &config.BizConfig{},
		templateVersionSvc: newTestTemplateVersionSvc(t),
	}

	image := "385595570414.dkr.ecr.us-west-2.amazonaws.com/tidbcloud/tikv:dedicated-next-gen"
	defaultConfig := schemav1.NewTomlConfig(map[string]any{})
	defaultConfig.Set("native-br.backup-interval", "60s")
	defaultConfig.Set("native-br.backup-tolerate-err", true)
	cfg, err := defaultConfig.MarshalTOML()
	if err != nil {
		t.Fatalf("failed to marshal default config: %v", err)
	}

	yaml, err := action.renderTiBRCR(context.Background(), initSharedPool4TiBRBackup_ALICLOUD(), "60205", image, string(cfg), "1", "4812Mi", testTiBRNodePlacement)
	require.NoError(t, err)
	assert.YAMLEq(t, tibrCRBackup_ALICLOUD, yaml)
}

const tibrCRBackup_ALICLOUD = `apiVersion: br.pingcap.com/v1alpha1
kind: TiBR
metadata:
  name: tibr-default
  namespace: tidb66e10e9ff65ef479654dde3968d3440d-0-0
spec:
  cluster:
    name: db
  image: 385595570414.dkr.ecr.us-west-2.amazonaws.com/tidbcloud/tikv:dedicated-next-gen
  config: |

    [native-br]
    backup-interval = "60s"
    backup-tolerate-err = true
  resources:
    cpu: 1
    memory: "4812Mi"
  overlay:
    pod:
      metadata:
        labels:
          app.kubernetes.io/instance: db-b598d9e9
          app.kubernetes.io/component: tibr
          tags.tidbcloud.com/tenant: "60205"
          tags.tidbcloud.com/component: tibr_backup
          tags.tidbcloud.com/resource-pool: "66e10e9ff65ef479654dde3968d3440d-0-0"
        annotations:
          prometheus.io/path: /metrics
          prometheus.io/port: "19500"
          prometheus.io/scrape: "true"
      spec:
        nodeSelector:
          node-spec: "4C16G"
          tidbcloud.com/node-pool-k8s-version: "1.33"
        priorityClassName: tidb-ng-component-critical
        serviceAccountName: tidb-cluster
        securityContext:
          runAsUser: 1000
          runAsGroup: 2000
          fsGroup: 2000
          runAsNonRoot: true
        volumes:
        - name: data
          emptyDir: {}
        - name: tmp-volume
          emptyDir: {}
        containers:
          - name: api-server
            env:
            - name: DFS_PREFIX
              value: 6841534A/cse
            - name: DFS_S3_BUCKET
              value: us-east-1-66e10e9ff65ef479654dde3968d3440d-0-0-data
            - name: DFS_S3_REGION
              value: us-east-1
            - name: DFS_S3_ENDPOINT
              value: oss-us-east-1-internal.aliyuncs.com
            - name: ALIBABA_CLOUD_REGION_ID
              value: us-east-1
            securityContext:
              readOnlyRootFilesystem: true
              allowPrivilegeEscalation: false
              privileged: false
              capabilities:
                drop:
                  - NET_RAW
            volumeMounts:
            - name: data
              mountPath: /data
            - name: tmp-volume
              mountPath: /tmp
            ports:
            - containerPort: 19500
              hostPort: 19500
              name: server
              protocol: TCP
        tolerations:
        - effect: NoSchedule
          key: dedicated
          operator: Equal
          value: worker
`

func initSharedPool4TiBRBackup_AWS() *entity.SharedPool {
	return &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Id: "66e10e9ff65ef479654dde3968d3440d-0-0",
			ComponentGroups: []*entity.Component{
				{
					Id:            "b598d9e9",
					ComponentType: apiv1.ComponentType_TIKV,
					Configuration: &entity.ComponentConfiguration{
						ImageRepo: "tikv",
						ImageTag:  "dedicated-next-gen",
					},
				},
			},
			ObjectStorage: &entity.ObjectStorage{
				Region: "us-east-1",
				Bucket: "us-east-1-66e10e9ff65ef479654dde3968d3440d-0-0-data",
				Prefix: "6841534A",
			},
		},
	}
}

func initSharedPool4TiBRBackup_ALICLOUD() *entity.SharedPool {
	return &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Id: "66e10e9ff65ef479654dde3968d3440d-0-0",
			ComponentGroups: []*entity.Component{
				{
					Id:            "b598d9e9",
					ComponentType: apiv1.ComponentType_TIKV,
					Configuration: &entity.ComponentConfiguration{
						ImageRepo: "tikv",
						ImageTag:  "dedicated-next-gen",
					},
				},
			},
			ObjectStorage: &entity.ObjectStorage{
				Region: "us-east-1",
				Bucket: "us-east-1-66e10e9ff65ef479654dde3968d3440d-0-0-data",
				Prefix: "6841534A",
			},
			Provider: &apiv1.Provider{
				Type:   apiv1.ProviderType_ALICLOUD,
				Region: "us-east-1",
			},
		},
	}
}

func initImageConfig4TiBRBackup() *config.ImageConfig {
	return &config.ImageConfig{
		Registry:   "385595570414.dkr.ecr.us-west-2.amazonaws.com",
		RepoPrefix: "tidbcloud",
	}
}

func TestGetResourcesForTiBRAutoBackup(t *testing.T) {
	tests := []struct {
		name          string
		bizConfig     *config.BizConfig
		expectedCPU   string
		expectedMem   string
		expectError   bool
		errorContains string
	}{
		{
			name:        "nil bizConfig uses defaults",
			bizConfig:   nil,
			expectedCPU: "1",      // DefaultTiBRCPU="2" -> 2000 milliCPU -> 2 cores -> ComputeAvailableCPU(2) = 1 (even)
			expectedMem: "4812Mi", // DefaultTiBRMemory="8Gi" -> 8192 MiB -> ComputeAvailableMemory(8192) = 4812 MiB
			expectError: false,
		},
		{
			name: "empty bizConfig uses defaults",
			bizConfig: &config.BizConfig{
				DefaultTiBRConfig: config.TiBRConfig{},
			},
			expectedCPU: "1",
			expectedMem: "4812Mi",
			expectError: false,
		},
		{
			name: "bizConfig with empty CPU and Memory uses defaults",
			bizConfig: &config.BizConfig{
				DefaultTiBRConfig: config.TiBRConfig{
					CPU:    "",
					Memory: "",
				},
			},
			expectedCPU: "1",
			expectedMem: "4812Mi",
			expectError: false,
		},
		{
			name: "bizConfig with custom CPU and Memory",
			bizConfig: &config.BizConfig{
				DefaultTiBRConfig: config.TiBRConfig{
					CPU:    "4",
					Memory: "16Gi",
				},
			},
			expectedCPU: "3",       // 4 cores -> ComputeAvailableCPU(4) = 3 (even)
			expectedMem: "13208Mi", // 16Gi = 16384 MiB -> ComputeAvailableMemory(16384) = 13208 MiB
			expectError: false,
		},
		{
			name: "bizConfig with CPU <= 1",
			bizConfig: &config.BizConfig{
				DefaultTiBRConfig: config.TiBRConfig{
					CPU:    "1",
					Memory: "8Gi",
				},
			},
			expectedCPU: "1", // 1 core -> ComputeAvailableCPU(1) = 1 (<=1)
			expectedMem: "4812Mi",
			expectError: false,
		},
		{
			name: "bizConfig with large Memory value",
			bizConfig: &config.BizConfig{
				DefaultTiBRConfig: config.TiBRConfig{
					CPU:    "2",
					Memory: "32Gi",
				},
			},
			expectedCPU: "1",
			expectedMem: "27585Mi", // 32Gi = 32768 MiB -> ComputeAvailableMemory(32768) = 27585 MiB
			expectError: false,
		},
		{
			name: "invalid CPU format",
			bizConfig: &config.BizConfig{
				DefaultTiBRConfig: config.TiBRConfig{
					CPU:    "invalid-cpu",
					Memory: "8Gi",
				},
			},
			expectError:   true,
			errorContains: "failed to parse cpu",
		},
		{
			name: "invalid Memory format",
			bizConfig: &config.BizConfig{
				DefaultTiBRConfig: config.TiBRConfig{
					CPU:    "2",
					Memory: "invalid-memory",
				},
			},
			expectError:   true,
			errorContains: "failed to parse memory",
		},
		{
			name: "bizConfig with only custom CPU",
			bizConfig: &config.BizConfig{
				DefaultTiBRConfig: config.TiBRConfig{
					CPU:    "6",
					Memory: "", // empty uses default
				},
			},
			expectedCPU: "5",      // 6 cores -> ComputeAvailableCPU(6) = 5 (even)
			expectedMem: "4812Mi", // uses default
			expectError: false,
		},
		{
			name: "bizConfig with only custom Memory",
			bizConfig: &config.BizConfig{
				DefaultTiBRConfig: config.TiBRConfig{
					CPU:    "", // empty uses default
					Memory: "12Gi",
				},
			},
			expectedCPU: "1",      // uses default
			expectedMem: "9522Mi", // 12Gi = 12288 MiB -> ComputeAvailableMemory(12288) = 9522 MiB
			expectError: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			cpu, memory, err := getResourcesForTiBRAutoBackup(tt.bizConfig)

			if tt.expectError {
				require.Error(t, err)
				if tt.errorContains != "" {
					assert.Contains(t, err.Error(), tt.errorContains)
				}
			} else {
				require.NoError(t, err)
				assert.Equal(t, tt.expectedCPU, cpu, "CPU mismatch")
				assert.Equal(t, tt.expectedMem, memory, "Memory mismatch")
			}
		})
	}
}
