package region

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"go.uber.org/mock/gomock"

	globalcommonv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/common/v1"
	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	"github.com/tidbcloud/tidb-management-service/nextgen-global-service/domain/adapter/regionalserver/mock_regionalserver"
	clusterrepoimpl "github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/cluster"
	regionrepoimpl "github.com/tidbcloud/tidb-management-service/nextgen-global-service/infra/repoimpl/region"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/idgenerator"
)

func Test_UpdateRegionOpsAuditLogSetting(t *testing.T) {
	dbPvd := *dbProvider.Load()
	regionRepo := regionrepoimpl.NewRegionRepo(dbPvd)
	clusterRepo := clusterrepoimpl.NewClusterRepo(dbPvd)

	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockRegionalServer := mock_regionalserver.NewMockClient(ctrl)

	providerIdentityRepo := regionrepoimpl.NewRegionProviderIdentityRepo(dbPvd)
	idGeneratorAdapter := idgenerator.NewSonyflakeID(1, "")

	svc := NewService(regionRepo, clusterRepo, providerIdentityRepo, nil, nil, dbPvd, mockRegionalServer, idGeneratorAdapter, nil, nil)

	regionId := globalcommonv1.Region_AWS_US_EAST_1
	logicRegionName := "us-east-1-ut-a0"
	orgId := uint64(222)

	mockRegionalServer.EXPECT().SetKongAuditLogSetting(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(1)

	_, err := svc.RegisterRegion(context.TODO(), &globalngv1.RegisterRegionRequest{
		Region: &globalcommonv1.Region{
			Id:              regionId,
			LogicalName:     logicRegionName,
			CloudProviderId: globalcommonv1.CloudProvider_AWS,
			DisplayName:     "US East 1",
			OpenedTenantIds: []uint64{orgId},
			SortPriority:    100,
			IsPublic:        false,
			State:           globalcommonv1.Region_ACTIVE,
			ServicePlan:     globalcommonv1.ServicePlan_BYOC,
			OpsAuditLogSetting: &globalcommonv1.RegionOpsAuditLogSetting{
				Enabled: true,
				LogLocation: &globalcommonv1.RegionOpsAuditLogSetting_LogLocation{
					Type:       globalcommonv1.RegionOpsAuditLogSetting_TYPE_STANDARD,
					BucketUri:  "s3://initial-bucket",
					RoleArn:    "arn:initial-role",
					ExternalId: "initial-external-id",
				},
				LogRotation: &globalcommonv1.RegionOpsAuditLogSetting_LogRotation{
					RotationIntervalInMin: 15,
					FileSizeLimitInMb:     25,
				},
			},
		},
		RegionProviderIdentities: []*globalngv1.RegionProviderIdentity{
			{Identity: "test2"},
		},
	})
	require.NoError(t, err, "Failed to register initial Region")
	defer func() {
		_, deleteErr := svc.DeleteRegion(context.Background(), &globalngv1.DeleteRegionRequest{
			RegionLogicalName: logicRegionName,
		})
		require.NoError(t, deleteErr)
	}()

	t.Run("Success_Update_Only_Enabled_Status", func(t *testing.T) {
		mockRegionalServer.EXPECT().SetKongAuditLogSetting(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(1)

		req := &globalngv1.UpdateRegionOpsAuditLogSettingRequest{
			Region: &globalngv1.RegionIdentifier{
				RegionIdentifier: &globalngv1.RegionIdentifier_RegionLogicalName{
					RegionLogicalName: logicRegionName,
				},
			},
			OpsAuditLogSetting: &globalcommonv1.RegionOpsAuditLogSetting{
				Enabled: true,
			},
		}

		resp, err := svc.UpdateRegionOpsAuditLogSetting(context.TODO(), req)
		require.NoError(t, err)

		assert.True(t, resp.Region.OpsAuditLogSetting.Enabled)
		assert.Equal(t, uint64(15), resp.Region.OpsAuditLogSetting.LogRotation.GetRotationIntervalInMin())
	})

	t.Run("Success_Update_By_RegionID_FullUpdate_StandardType", func(t *testing.T) {
		mockRegionalServer.EXPECT().SetKongAuditLogSetting(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).Times(1)

		req := &globalngv1.UpdateRegionOpsAuditLogSettingRequest{
			Region: &globalngv1.RegionIdentifier{
				RegionIdentifier: &globalngv1.RegionIdentifier_RegionLogicalName{
					RegionLogicalName: logicRegionName,
				},
			},
			OpsAuditLogSetting: &globalcommonv1.RegionOpsAuditLogSetting{
				Enabled: false,
				LogLocation: &globalcommonv1.RegionOpsAuditLogSetting_LogLocation{
					Type:       globalcommonv1.RegionOpsAuditLogSetting_TYPE_STANDARD,
					BucketUri:  "s3://updated-bucket-regionid",
					RoleArn:    "arn:updated-role-regionid",
					ExternalId: "updated-external-id-regionid",
				},
				LogRotation: &globalcommonv1.RegionOpsAuditLogSetting_LogRotation{
					RotationIntervalInMin: 60,
					FileSizeLimitInMb:     100,
				},
			},
		}

		resp, err := svc.UpdateRegionOpsAuditLogSetting(context.TODO(), req)
		require.NoError(t, err)

		assert.False(t, resp.Region.OpsAuditLogSetting.Enabled)
		assert.Equal(t, "s3://updated-bucket-regionid", resp.Region.OpsAuditLogSetting.LogLocation.BucketUri)
		assert.Equal(t, uint64(60), resp.Region.OpsAuditLogSetting.LogRotation.RotationIntervalInMin)

		getResp, _ := svc.GetRegionByLogicalName(context.TODO(), &globalngv1.GetRegionByLogicalNameRequest{RegionLogicalName: logicRegionName})
		assert.False(t, getResp.OpsAuditLogSetting.Enabled)
		assert.Equal(t, "s3://updated-bucket-regionid", getResp.OpsAuditLogSetting.LogLocation.BucketUri)
	})

	t.Run("Error_Region_Not_Found", func(t *testing.T) {
		req := &globalngv1.UpdateRegionOpsAuditLogSettingRequest{
			Region: &globalngv1.RegionIdentifier{
				RegionIdentifier: &globalngv1.RegionIdentifier_RegionLogicalName{
					RegionLogicalName: "region_not_found",
				},
			},
			OpsAuditLogSetting: &globalcommonv1.RegionOpsAuditLogSetting{
				Enabled: true,
			},
		}

		_, err := svc.UpdateRegionOpsAuditLogSetting(context.TODO(), req)
		require.Error(t, err)
		assert.Equal(t, bizerr.ErrCodeRecordNotFound, err.ErrCode())
		assert.Contains(t, err.Error(), "record not found")
	})

	t.Run("Error_SetKongAuditLogSetting_Failure", func(t *testing.T) {
		expectedError := bizerr.New(bizerr.GeneralInternalErrorCode, "update failed")
		mockRegionalServer.EXPECT().SetKongAuditLogSetting(gomock.Any(), gomock.Any(), gomock.Any()).Return(expectedError).Times(1)

		req := &globalngv1.UpdateRegionOpsAuditLogSettingRequest{
			Region: &globalngv1.RegionIdentifier{
				RegionIdentifier: &globalngv1.RegionIdentifier_OrgRegion{
					OrgRegion: &globalngv1.SelectRegionByOrgIdRequest{
						OrganizationId:  orgId,
						CloudProviderId: globalcommonv1.CloudProvider_AWS,
						RegionId:        regionId,
						ServicePlan:     globalcommonv1.ServicePlan_BYOC,
					},
				},
			},
			OpsAuditLogSetting: &globalcommonv1.RegionOpsAuditLogSetting{
				Enabled: true,
			},
		}

		_, err := svc.UpdateRegionOpsAuditLogSetting(context.TODO(), req)
		require.Error(t, err)
		assert.Equal(t, err.ErrCode(), bizerr.GeneralInternalErrorCode)
		assert.Contains(t, err.Error(), "update region ops audit log setting failed")

		getResp, _ := svc.GetRegionByLogicalName(context.TODO(), &globalngv1.GetRegionByLogicalNameRequest{RegionLogicalName: logicRegionName})
		assert.Equal(t, uint64(60), getResp.OpsAuditLogSetting.LogRotation.RotationIntervalInMin)
	})
}

// Test_scenario_OpsAuditLogSetting tests the full lifecycle of ops audit log settings for a region
func Test_scenario_OpsAuditLogSetting(t *testing.T) {
	dbPvd := *dbProvider.Load()
	regionRepo := regionrepoimpl.NewRegionRepo(dbPvd)

	ctrl := gomock.NewController(t)
	defer ctrl.Finish()

	mockRegionalServer := mock_regionalserver.NewMockClient(ctrl)

	providerIdentityRepo := regionrepoimpl.NewRegionProviderIdentityRepo(dbPvd)
	idGeneratorAdapter := idgenerator.NewSonyflakeID(1, "")

	svc := NewService(regionRepo, nil, providerIdentityRepo, nil, nil, dbPvd, mockRegionalServer, idGeneratorAdapter, nil, nil)

	mockRegionalServer.EXPECT().SetKongAuditLogSetting(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()

	r, err := svc.RegisterRegion(context.TODO(), &globalngv1.RegisterRegionRequest{
		Region: &globalcommonv1.Region{
			Id:              globalcommonv1.Region_AWS_US_WEST_2,
			LogicalName:     "us-west-2-oals",
			CloudProviderId: globalcommonv1.CloudProvider_AWS,
			DisplayName:     "US West 2",
			OpenedTenantIds: []uint64{111},
			SortPriority:    100,
			IsPublic:        false,
			State:           globalcommonv1.Region_ACTIVE,
			ServicePlan:     globalcommonv1.ServicePlan_BYOC,
			OpsAuditLogSetting: &globalcommonv1.RegionOpsAuditLogSetting{
				Enabled: true,
				LogLocation: &globalcommonv1.RegionOpsAuditLogSetting_LogLocation{
					Type:       globalcommonv1.RegionOpsAuditLogSetting_TYPE_STANDARD,
					BucketUri:  "s3://my-audit-logs-bucket",
					RoleArn:    "arn:aws:iam::123456789012:role/TidbCloudAuditLogRole",
					ExternalId: "external-id-123",
				},
				LogRotation: &globalcommonv1.RegionOpsAuditLogSetting_LogRotation{
					RotationIntervalInMin: 10,
					FileSizeLimitInMb:     20,
				},
			},
		},
		RegionProviderIdentities: []*globalngv1.RegionProviderIdentity{
			{Identity: "test3"},
		},
	})
	require.NoError(t, err)
	assert.Equal(t, globalcommonv1.Region_AWS_US_WEST_2, r.Id)
	assert.Equal(t, globalcommonv1.CloudProvider_AWS, r.CloudProviderId)
	assert.Equal(t, globalcommonv1.ServicePlan_BYOC, r.ServicePlan)
	assert.True(t, r.OpsAuditLogSetting.Enabled)
	assert.Equal(t, globalcommonv1.RegionOpsAuditLogSetting_TYPE_STANDARD, r.OpsAuditLogSetting.LogLocation.Type)
	assert.Equal(t, "s3://my-audit-logs-bucket", r.OpsAuditLogSetting.LogLocation.BucketUri)
	assert.Equal(t, "arn:aws:iam::123456789012:role/TidbCloudAuditLogRole", r.OpsAuditLogSetting.LogLocation.RoleArn)
	assert.Equal(t, "external-id-123", r.OpsAuditLogSetting.LogLocation.ExternalId)
	assert.Equal(t, uint64(10), r.OpsAuditLogSetting.LogRotation.RotationIntervalInMin)
	assert.Equal(t, uint64(20), r.OpsAuditLogSetting.LogRotation.FileSizeLimitInMb)

	// Get the ops audit log setting
	getRegionResp, err := svc.SelectRegionByOrgId(context.Background(), &globalngv1.SelectRegionByOrgIdRequest{
		OrganizationId:  111,
		CloudProviderId: globalcommonv1.CloudProvider_AWS,
		RegionId:        globalcommonv1.Region_AWS_US_WEST_2,
		ServicePlan:     globalcommonv1.ServicePlan_BYOC,
	})
	require.NoError(t, err)
	assert.Equal(t, globalcommonv1.Region_AWS_US_WEST_2, getRegionResp.Id)
	assert.Equal(t, globalcommonv1.CloudProvider_AWS, getRegionResp.CloudProviderId)
	assert.Equal(t, globalcommonv1.ServicePlan_BYOC, getRegionResp.ServicePlan)
	assert.True(t, getRegionResp.OpsAuditLogSetting.Enabled)
	assert.Equal(t, globalcommonv1.RegionOpsAuditLogSetting_TYPE_STANDARD, getRegionResp.OpsAuditLogSetting.LogLocation.Type)
	assert.Equal(t, "s3://my-audit-logs-bucket", getRegionResp.OpsAuditLogSetting.LogLocation.BucketUri)
	assert.Equal(t, "arn:aws:iam::123456789012:role/TidbCloudAuditLogRole", getRegionResp.OpsAuditLogSetting.LogLocation.RoleArn)
	assert.Equal(t, "external-id-123", getRegionResp.OpsAuditLogSetting.LogLocation.ExternalId)
	assert.Equal(t, uint64(10), getRegionResp.OpsAuditLogSetting.LogRotation.RotationIntervalInMin)
	assert.Equal(t, uint64(20), getRegionResp.OpsAuditLogSetting.LogRotation.FileSizeLimitInMb)

	// Update the ops audit log setting by region logical name
	updatedResp, err := svc.UpdateRegionOpsAuditLogSetting(context.TODO(), &globalngv1.UpdateRegionOpsAuditLogSettingRequest{
		Region: &globalngv1.RegionIdentifier{
			RegionIdentifier: &globalngv1.RegionIdentifier_RegionLogicalName{
				RegionLogicalName: "us-west-2-oals",
			},
		},
		OpsAuditLogSetting: &globalcommonv1.RegionOpsAuditLogSetting{
			Enabled: false,
			LogLocation: &globalcommonv1.RegionOpsAuditLogSetting_LogLocation{
				Type:       globalcommonv1.RegionOpsAuditLogSetting_TYPE_STANDARD,
				BucketUri:  "s3://my-audit-logs-bucket2",
				RoleArn:    "arn:aws:iam::123456789012:role/TidbCloudAuditLogRole2",
				ExternalId: "external-id-123xxxxx",
			},
			LogRotation: &globalcommonv1.RegionOpsAuditLogSetting_LogRotation{
				RotationIntervalInMin: 30,
				FileSizeLimitInMb:     40,
			},
		},
	})
	require.NoError(t, err)
	assert.False(t, updatedResp.Region.OpsAuditLogSetting.Enabled)
	assert.Equal(t, "s3://my-audit-logs-bucket2", updatedResp.Region.OpsAuditLogSetting.LogLocation.BucketUri)
	assert.Equal(t, "arn:aws:iam::123456789012:role/TidbCloudAuditLogRole2", updatedResp.Region.OpsAuditLogSetting.LogLocation.RoleArn)
	assert.Equal(t, "external-id-123", updatedResp.Region.OpsAuditLogSetting.LogLocation.ExternalId)
	assert.Equal(t, uint64(30), updatedResp.Region.OpsAuditLogSetting.LogRotation.RotationIntervalInMin)
	assert.Equal(t, uint64(40), updatedResp.Region.OpsAuditLogSetting.LogRotation.FileSizeLimitInMb)

	// Get the updated ops audit log setting
	getRegionResp, err = svc.GetRegionByLogicalName(context.TODO(), &globalngv1.GetRegionByLogicalNameRequest{
		RegionLogicalName: "us-west-2-oals",
	})
	require.NoError(t, err)
	assert.False(t, getRegionResp.OpsAuditLogSetting.Enabled)
	assert.Equal(t, "s3://my-audit-logs-bucket2", getRegionResp.OpsAuditLogSetting.LogLocation.BucketUri)
	assert.Equal(t, "arn:aws:iam::123456789012:role/TidbCloudAuditLogRole2", getRegionResp.OpsAuditLogSetting.LogLocation.RoleArn)
	assert.Equal(t, "external-id-123", getRegionResp.OpsAuditLogSetting.LogLocation.ExternalId)
	assert.Equal(t, uint64(30), getRegionResp.OpsAuditLogSetting.LogRotation.RotationIntervalInMin)
	assert.Equal(t, uint64(40), getRegionResp.OpsAuditLogSetting.LogRotation.FileSizeLimitInMb)

	// Update the ops audit log setting by
	updatedResp, err = svc.UpdateRegionOpsAuditLogSetting(context.TODO(), &globalngv1.UpdateRegionOpsAuditLogSettingRequest{
		Region: &globalngv1.RegionIdentifier{
			RegionIdentifier: &globalngv1.RegionIdentifier_OrgRegion{
				OrgRegion: &globalngv1.SelectRegionByOrgIdRequest{
					OrganizationId:  111,
					CloudProviderId: globalcommonv1.CloudProvider_AWS,
					RegionId:        globalcommonv1.Region_AWS_US_WEST_2,
					ServicePlan:     globalcommonv1.ServicePlan_BYOC,
				},
			},
		},
		OpsAuditLogSetting: &globalcommonv1.RegionOpsAuditLogSetting{
			Enabled: false,
			LogLocation: &globalcommonv1.RegionOpsAuditLogSetting_LogLocation{
				Type:       globalcommonv1.RegionOpsAuditLogSetting_TYPE_STANDARD,
				BucketUri:  "s3://my-audit-logs-bucket3",
				RoleArn:    "arn:aws:iam::123456789012:role/TidbCloudAuditLogRole3",
				ExternalId: "external-id-123xxxxx",
			},
			LogRotation: &globalcommonv1.RegionOpsAuditLogSetting_LogRotation{
				RotationIntervalInMin: 50,
				FileSizeLimitInMb:     60,
			},
		},
	})
	require.NoError(t, err)
	assert.False(t, updatedResp.Region.OpsAuditLogSetting.Enabled)
	assert.Equal(t, "s3://my-audit-logs-bucket3", updatedResp.Region.OpsAuditLogSetting.LogLocation.BucketUri)
	assert.Equal(t, "arn:aws:iam::123456789012:role/TidbCloudAuditLogRole3", updatedResp.Region.OpsAuditLogSetting.LogLocation.RoleArn)
	assert.Equal(t, "external-id-123", updatedResp.Region.OpsAuditLogSetting.LogLocation.ExternalId)
	assert.Equal(t, uint64(50), updatedResp.Region.OpsAuditLogSetting.LogRotation.RotationIntervalInMin)
	assert.Equal(t, uint64(60), updatedResp.Region.OpsAuditLogSetting.LogRotation.FileSizeLimitInMb)
}
