package cluster

import (
	"context"
	"testing"

	"github.com/aws/aws-sdk-go-v2/aws/signer/v4"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/mock/gomock"

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/adapter/awss3/mock_awss3"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/cluster/mock_cluster"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/repository/resourcepool/mock_resourcepool"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/pkg/config"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
)

func TestGetCaCertificateDownloadUrl(t *testing.T) {
	const (
		testClusterID  uint64 = 1001
		testPoolID     string = "500"
		testBucket            = "test-bucket"
		testDefaultKey        = "certs/default.pem"
		testPcaArn            = "arn:aws:acm-pca:us-east-1:123456789012:certificate-authority/abc"
		testPcaKey            = "certs/pca-custom.pem"
		testPoolKey           = "certs/pool-custom.pem"
		testPresignURL        = "https://s3.amazonaws.com/test-bucket/certs/test.pem?presigned"
	)

	tests := []struct {
		name      string
		cfg       config.ClusterCertsConfig
		cluster   *entity.Cluster
		poolSetup func(mock *mock_resourcepool.MockRepo)
		wantErr   bool
		errMsg    string
		wantURL   string
	}{
		{
			name: "pca_arn resolved from resource pool",
			cfg: config.ClusterCertsConfig{
				Bucket:     testBucket,
				DefaultKey: testDefaultKey,
				PcaArnCertKeyMappings: map[string]string{
					testPcaArn: testPcaKey,
				},
			},
			cluster: &entity.Cluster{
				Cluster: &globalngv1.Cluster{
					ClusterId:    testClusterID,
					SharedPoolId: testPoolID,
				},
			},
			poolSetup: func(mock *mock_resourcepool.MockRepo) {
				mock.EXPECT().Load(gomock.Any(), uint64(500), false).Return(&entity.ResourcePool{
					ResourcePool: &globalngv1.ResourcePool{
						PcaArn: testPcaArn,
					},
				}, nil)
			},
			wantURL: testPresignURL,
		},
		{
			name: "pca_arn set but no mapping in config",
			cfg: config.ClusterCertsConfig{
				Bucket:                testBucket,
				DefaultKey:            testDefaultKey,
				PcaArnCertKeyMappings: map[string]string{},
			},
			cluster: &entity.Cluster{
				Cluster: &globalngv1.Cluster{
					ClusterId:    testClusterID,
					SharedPoolId: testPoolID,
				},
			},
			poolSetup: func(mock *mock_resourcepool.MockRepo) {
				mock.EXPECT().Load(gomock.Any(), uint64(500), false).Return(&entity.ResourcePool{
					ResourcePool: &globalngv1.ResourcePool{
						PcaArn: testPcaArn,
					},
				}, nil)
			},
			wantErr: true,
			errMsg:  "no cert key mapping found for PCA ARN",
		},
		{
			name: "pca_arn set but PcaArnCertKeyMappings is nil",
			cfg: config.ClusterCertsConfig{
				Bucket:                testBucket,
				DefaultKey:            testDefaultKey,
				PcaArnCertKeyMappings: nil,
			},
			cluster: &entity.Cluster{
				Cluster: &globalngv1.Cluster{
					ClusterId:    testClusterID,
					SharedPoolId: testPoolID,
				},
			},
			poolSetup: func(mock *mock_resourcepool.MockRepo) {
				mock.EXPECT().Load(gomock.Any(), uint64(500), false).Return(&entity.ResourcePool{
					ResourcePool: &globalngv1.ResourcePool{
						PcaArn: testPcaArn,
					},
				}, nil)
			},
			wantErr: true,
			errMsg:  "PcaArnCertKeyMappings is not configured",
		},
		{
			name: "resource pool has empty pca_arn falls through to ResourcePoolCertKeyMappings",
			cfg: config.ClusterCertsConfig{
				Bucket:     testBucket,
				DefaultKey: testDefaultKey,
				ResourcePoolCertKeyMappings: map[string]string{
					testPoolID: testPoolKey,
				},
			},
			cluster: &entity.Cluster{
				Cluster: &globalngv1.Cluster{
					ClusterId:    testClusterID,
					SharedPoolId: testPoolID,
				},
			},
			poolSetup: func(mock *mock_resourcepool.MockRepo) {
				mock.EXPECT().Load(gomock.Any(), uint64(500), false).Return(&entity.ResourcePool{
					ResourcePool: &globalngv1.ResourcePool{
						PcaArn: "",
					},
				}, nil)
			},
			wantURL: testPresignURL,
		},
		{
			name: "resource pool without PCA ARN and no ResourcePoolCertKeyMappings falls through to default key",
			cfg: config.ClusterCertsConfig{
				Bucket:     testBucket,
				DefaultKey: testDefaultKey,
			},
			cluster: &entity.Cluster{
				Cluster: &globalngv1.Cluster{
					ClusterId:    testClusterID,
					SharedPoolId: testPoolID,
				},
			},
			poolSetup: func(mock *mock_resourcepool.MockRepo) {
				mock.EXPECT().Load(gomock.Any(), uint64(500), false).Return(&entity.ResourcePool{
					ResourcePool: &globalngv1.ResourcePool{
						PcaArn: "",
					},
				}, nil)
			},
			wantURL: testPresignURL,
		},
	}

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

			mockClusterRepo := mock_cluster.NewMockRepo(ctrl)
			mockResourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)
			mockS3 := mock_awss3.NewMockPresignClient(ctrl)

			mockClusterRepo.EXPECT().Get(gomock.Any(), testClusterID, false).Return(tt.cluster, nil)

			tt.poolSetup(mockResourcePoolRepo)

			if !tt.wantErr {
				mockS3.EXPECT().PresignGetObject(gomock.Any(), gomock.Any(), gomock.Any()).
					Return(&v4.PresignedHTTPRequest{URL: testPresignURL}, nil)
			}

			svc := &serviceImpl{
				clusterRepo:      mockClusterRepo,
				resourcePoolRepo: mockResourcePoolRepo,
				s3PresignClient:  mockS3,
				cfg: &config.Config{
					BizConfig: config.BizConfig{
						ClusterCertsConfig: tt.cfg,
					},
				},
			}

			result, err := svc.GetCaCertificateDownloadUrl(context.Background(), &globalngv1.GetCaCertificateDownloadUrlRequest{
				ClusterId: testClusterID,
			})

			if tt.wantErr {
				require.NotNil(t, err)
				assert.Contains(t, err.Error(), tt.errMsg)
				assert.Nil(t, result)
			} else {
				require.Nil(t, err)
				require.NotNil(t, result)
				assert.Equal(t, tt.wantURL, result.Uri)
			}
		})
	}
}

func TestGetCaCertificateDownloadUrl_ResourcePoolLoadError(t *testing.T) {
	ctrl := gomock.NewController(t)

	mockClusterRepo := mock_cluster.NewMockRepo(ctrl)
	mockResourcePoolRepo := mock_resourcepool.NewMockRepo(ctrl)

	cluster := &entity.Cluster{
		Cluster: &globalngv1.Cluster{
			ClusterId:    1001,
			SharedPoolId: "500",
		},
	}
	mockClusterRepo.EXPECT().Get(gomock.Any(), uint64(1001), false).Return(cluster, nil)
	mockResourcePoolRepo.EXPECT().Load(gomock.Any(), uint64(500), false).
		Return(nil, bizerr.New(bizerr.GeneralInternalErrorCode, "db error"))

	svc := &serviceImpl{
		clusterRepo:      mockClusterRepo,
		resourcePoolRepo: mockResourcePoolRepo,
		cfg: &config.Config{
			BizConfig: config.BizConfig{
				ClusterCertsConfig: config.ClusterCertsConfig{
					Bucket:     "test-bucket",
					DefaultKey: "default.pem",
				},
			},
		},
	}

	result, err := svc.GetCaCertificateDownloadUrl(context.Background(), &globalngv1.GetCaCertificateDownloadUrlRequest{
		ClusterId: 1001,
	})

	require.NotNil(t, err)
	assert.Nil(t, result)
	assert.Contains(t, err.Error(), "db error")
}
