package auditlog

import (
	"context"
	"fmt"
	"strconv"

	"github.com/samber/lo"
	"github.com/tidbcloud/tiinf/sdk/auditlog"

	globalngv1 "github.com/tidbcloud/tidb-management-service/api/spec/global/nextgen/v1"
	openapicommonv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/common/v1beta1"
	openapingv1beta2 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/nextgen/v1beta2"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/eventbus"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/entity"
	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/pkg/common"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/enum"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

func (s serverImpl) GetAuditLogSetting(
	ctx context.Context,
	request *openapingv1beta2.GetAuditLogSettingRequest,
) (*openapingv1beta2.AuditLogSetting, error) {
	ctx, span := tracer.Start(ctx, "GetAuditLogSetting")
	defer span.End()

	// Extract request meta.
	_, err := metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	tidbId, err := strconv.ParseUint(request.GetTidbId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid tidb id")
	}

	cluster, err := s.clusterSvc.GetCluster(ctx, tidbId, entity.ClusterViewClusterViewLocal, false)
	if err != nil {
		return nil, err
	}

	resp, err := s.nextgenCli.GetAuditLogSetting(ctx, &globalngv1.GetAuditLogSettingRequest{
		ClusterId: tidbId,
	})
	if err != nil {
		return nil, err
	}

	openApiConfig, err := configGlobalToOpenApi(resp, cluster.CloudProvider, resp.GetEnabled())
	if err != nil {
		return nil, err
	}
	if resp.GetEnabled() {
		_, err = s.nextgenCli.UpdateAuditLogSetting(
			ctx,
			&globalngv1.UpdateAuditLogSettingRequest{AuditLogSetting: resp, ValidateOnly: true},
		)
		if err != nil {
			berr, ok := bizerr.AsCode(err, bizerr.ErrCodePermission)
			if !ok {
				return nil, bizerr.WrapError(err, "failed to check bucket write permission")
			}
			openApiConfig.BucketWriteCheck = &openapingv1beta2.AuditLogSetting_BucketWriteCheck{
				Writable:    false,
				ErrorReason: berr.ErrMsg(),
			}
		}
	}

	return openApiConfig, nil
}

func (s serverImpl) UpdateAuditLogSetting(
	ctx context.Context,
	request *openapingv1beta2.UpdateAuditLogSettingRequest,
) (*openapingv1beta2.AuditLogSetting, error) {
	ctx, span := tracer.Start(ctx, "UpdateAuditLogSetting")
	defer span.End()

	var (
		cluster *entity.Cluster
		err     error
		reqMeta *metadata.RequestMeta
	)

	defer func() {
		if !request.GetValidateOnly() {
			s.auditLogSender.SubmitAuditLogForNextgen(ctx, auditlog.EventUpdateAuditLogConfig, reqMeta, func() auditlog.Details {
				details := eventbus.NewAuditLogDetails(eventbus.TierTypePremium)
				details = eventbus.AppendDbAuditLogDetails(details, cluster.ClusterId, cluster.DisplayName, request.GetAuditLogSetting())
				return details
			}, err)
		}
	}()

	// Extract request meta.
	reqMeta, err = metadata.ExtractRequestMetaWithCheckPermission(ctx)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to extract request metadata")
	}

	// Get the global cluster.
	clusterId, err := strconv.ParseUint(request.GetTidbId(), 10, 64)
	if err != nil {
		return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "invalid cluster id")
	}
	cluster, err = s.clusterSvc.GetCluster(ctx, clusterId, entity.ClusterViewClusterViewLocal, false)
	if err != nil {
		return nil, err
	}

	globalConfig, err := convertOpenAPISettingToGlobal(clusterId, request.GetAuditLogSetting())
	if err != nil {
		return nil, err
	}

	// Do update finally.
	resp, err := s.nextgenCli.UpdateAuditLogSetting(ctx, &globalngv1.UpdateAuditLogSettingRequest{
		AuditLogSetting: globalConfig,
		ValidateOnly:    request.GetValidateOnly(),
	})
	if err != nil {
		return nil, err
	}

	// Convert the global config to openapi config.
	openApiConfig, err := configGlobalToOpenApi(resp, cluster.CloudProvider, resp.GetEnabled())
	if err != nil {
		return nil, err
	}

	return openApiConfig, nil
}

func configGlobalToOpenApi(
	c *globalngv1.AuditLogSetting,
	provider openapicommonv1beta1.Region_CloudProvider,
	enabled bool,
) (*openapingv1beta2.AuditLogSetting, bizerr.BizError) {
	_, regionId, _, err := common.BuildOpenApiRegionName(provider.String(), c.GetRegion())
	if err != nil {
		return nil, bizerr.WrapError(err, "failed to build region name")
	}
	format, err := enum.FromString[openapingv1beta2.AuditLogSetting_Format](c.GetFormat())
	if err != nil {
		return nil, bizerr.WrapError(err, "failed to parse format")
	}
	output := &openapingv1beta2.AuditLogSetting{
		Name:    fmt.Sprintf("clusters/%d/auditLogConfig", c.GetClusterId()),
		TidbId:  strconv.FormatUint(c.GetClusterId(), 10),
		Enabled: lo.ToPtr(c.GetEnabled()),
		StorageSetting: &openapingv1beta2.AuditLogSetting_StorageSetting{
			BucketUri:         c.GetBucketUrl(),
			BucketRegionId:    regionId,
			CloudRoleIdentity: c.CloudRoleIdentity,
		},
		Unredacted: c.Unredacted,
		Format:     openapingv1beta2.AuditLogSetting_Format(format),
		RotationPolicy: &openapingv1beta2.AuditLogSetting_RotationPolicy{
			MaxSizeMib:   c.GetMaxSizeMib(),
			MaxAgeSecond: lo.ToPtr(c.GetMaxAgeSecond()),
		},
	}
	if enabled {
		output.BucketWriteCheck = &openapingv1beta2.AuditLogSetting_BucketWriteCheck{
			Writable: true, // Default to true, will be set to false if write permission check fails.
		}
	}
	return output, nil
}

func convertOpenAPISettingToGlobal(
	clusterID uint64,
	setting *openapingv1beta2.AuditLogSetting,
) (*globalngv1.AuditLogSetting, bizerr.BizError) {
	if setting == nil {
		return nil, bizerr.New(bizerr.ErrCodeParams, "audit log setting is required")
	}

	globalConfig := &globalngv1.AuditLogSetting{
		ClusterId:  clusterID,
		Enabled:    setting.Enabled,
		Unredacted: setting.Unredacted,
		Format:     setting.GetFormat().String(),
	}

	storageSetting := setting.GetStorageSetting()
	if storageSetting != nil {
		globalConfig.BucketUrl = storageSetting.GetBucketUri()
		globalConfig.CloudRoleIdentity = storageSetting.CloudRoleIdentity
		if storageSetting.GetBucketRegionId() != "" {
			_, regionCode, err := common.ParseOpenApiRegionNameOrId(storageSetting.GetBucketRegionId())
			if err != nil {
				return nil, bizerr.WrapErrorWithCode(err, bizerr.ErrCodeParams, "failed to parse region name")
			}
			globalConfig.Region = regionCode
		}
	}

	rotationPolicy := setting.GetRotationPolicy()
	if rotationPolicy != nil {
		globalConfig.MaxSizeMib = rotationPolicy.GetMaxSizeMib()
		globalConfig.MaxAgeSecond = rotationPolicy.MaxAgeSecond
	}

	return globalConfig, nil
}
