package auditlog_svc

import (
	"context"

	"github.com/tidbcloud/pkgv2/bizerr"
	"github.com/tidbcloud/pkgv2/log"
	"github.com/tidbcloud/pkgv2/tracing"
	"github.com/tidbcloud/serverless-service/idl/pbgen/proto/v1beta1"
	"github.com/tidbcloud/serverless-service/internal/domain/facade"
	"github.com/tidbcloud/serverless-service/internal/domain/service"
	"github.com/tidbcloud/serverless-service/internal/domain/vo"
	"github.com/tidbcloud/serverless-service/internal/errorcode"
	aws2 "github.com/tidbcloud/serverless-service/internal/infra/aws"
	"github.com/tidbcloud/serverless-service/internal/utils"
	"go.opentelemetry.io/otel"
	"go.opentelemetry.io/otel/attribute"
	"go.uber.org/zap"
)

var v1beta1Tracer = otel.Tracer("auditlog.v1beta1")

const (
	listAuditLogsDefaultPageSize = 100
	listAuditLogsMaxPageSize     = 1000
	downloadAuditLogsMaxNumber   = 100
)

type databaseAuditLogV1beta1Service struct {
	mgmtsvcFacade     facade.MgmtSvcFacade
	s3ClientProvider  *aws2.S3ClientProvider
	preSignerProvider *aws2.PresignerProvider
	auditLogSvc       service.AuditLogService
	v1beta1.UnimplementedDatabaseAuditLogServiceServer
}

func NewDatabaseAuditLogV1beta1Service(mgmtsvcFacade facade.MgmtSvcFacade, s3ClientProvider *aws2.S3ClientProvider,
	preSignerProvider *aws2.PresignerProvider, auditLogSvc service.AuditLogService,
) v1beta1.DatabaseAuditLogServiceServer {
	return &databaseAuditLogV1beta1Service{
		mgmtsvcFacade:     mgmtsvcFacade,
		s3ClientProvider:  s3ClientProvider,
		preSignerProvider: preSignerProvider,
		auditLogSvc:       auditLogSvc,
	}
}

func (a *databaseAuditLogV1beta1Service) ListAuditLogFiles(
	ctx context.Context,
	request *v1beta1.ListAuditLogFilesRequest,
) (_ *v1beta1.ListAuditLogFilesResponse, err error) {
	ctx, span := v1beta1Tracer.Start(ctx, "databaseAuditLogV1beta1Service.ListAuditLogFiles")
	logger := log.WithContextL(ctx).With(zap.String("cluster_id", request.ClusterId))
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PrepareAuditLogErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("clusterId", request.ClusterId),
	)
	logger.Info("start to list audit log files")
	// check request
	switch {
	case request.PageSize < 0:
		return nil, bizerr.New(bizerr.ErrCodeParams, "page size must be positive")
	case request.PageSize == 0:
		request.PageSize = listAuditLogsDefaultPageSize
	case request.PageSize > listAuditLogsMaxPageSize:
		return nil, bizerr.New(
			bizerr.ErrCodeParams,
			"page size must be less than or equal to %d",
			listAuditLogsMaxPageSize,
		)
	}
	if request.Date == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "date cannot be empty")
	}
	// list audit logs
	auditLogs, nextPageToken, err := a.auditLogSvc.ListAuditLogFiles(
		ctx,
		request.ClusterId,
		request.PageToken,
		request.Date,
		request.PageSize,
	)
	if err != nil {
		return nil, err
	}

	// convert to response
	auditLogFiles := make([]*v1beta1.AuditLogFile, 0, len(auditLogs))
	for _, file := range auditLogs {
		auditLogFiles = append(auditLogFiles, file.ConvertToV1beta1())
	}
	return &v1beta1.ListAuditLogFilesResponse{
		AuditLogFiles: auditLogFiles,
		NextPageToken: nextPageToken,
	}, nil
}

func (a *databaseAuditLogV1beta1Service) DownloadAuditLogFiles(
	ctx context.Context,
	request *v1beta1.DownloadAuditLogFilesRequest,
) (_ *v1beta1.DownloadAuditLogFilesResponse, err error) {
	ctx, span := v1beta1Tracer.Start(ctx, "databaseAuditLogV1beta1Service.DownloadAuditLogFiles")
	logger := log.WithContextL(ctx).With(zap.String("cluster_id", request.ClusterId))
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PrepareAuditLogErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("clusterId", request.ClusterId),
	)
	logger.Info("start to download audit log files")
	// check request
	if len(request.AuditLogFileNames) > downloadAuditLogsMaxNumber {
		return nil, bizerr.New(errorcode.ErrCodeDownloadExceedMaxNumber, "")
	}
	// download audit logs
	auditLogs, err := a.auditLogSvc.DownloadAuditLogFiles(ctx, request.ClusterId, request.AuditLogFileNames)
	if err != nil {
		return nil, err
	}
	auditLogFiles := make([]*v1beta1.AuditLogFile, 0, len(auditLogs))
	for _, file := range auditLogs {
		auditLogFiles = append(auditLogFiles, file.ConvertToV1beta1())
	}
	return &v1beta1.DownloadAuditLogFilesResponse{
		AuditLogFiles: auditLogFiles,
	}, nil
}

func (a *databaseAuditLogV1beta1Service) GetAuditLogFilterRule(
	ctx context.Context,
	request *v1beta1.GetAuditLogFilterRuleRequest,
) (_ *v1beta1.AuditLogFilterRule, err error) {
	ctx, span := v1beta1Tracer.Start(ctx, "databaseAuditLogV1beta1Service.GetAuditLogFilterRule")
	logger := log.WithContextL(ctx).With(zap.String("filter_rule_id", request.FilterRuleId))
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PrepareAuditLogErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("filterRuleId", request.FilterRuleId),
	)
	logger.Info("start to get audit log filter rule")
	if request.FilterRuleId == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty cluster id or filter rule id")
	}

	filterRule, err := a.auditLogSvc.GetAuditLogFilterRule(ctx, request.ClusterId, request.FilterRuleId)
	if err != nil {
		return nil, err
	}
	return vo.FilterRuleToV1beta1(filterRule), nil
}

func (a *databaseAuditLogV1beta1Service) ListAuditLogFilterRules(
	ctx context.Context,
	request *v1beta1.ListAuditLogFilterRulesRequest,
) (_ *v1beta1.ListAuditLogFilterRulesResponse, err error) {
	ctx, span := v1beta1Tracer.Start(ctx, "databaseAuditLogV1beta1Service.ListAuditLogFilterRules")
	logger := log.WithContextL(ctx).With(zap.String("cluster_id", request.ClusterId))
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PrepareAuditLogErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("clusterId", request.ClusterId),
	)
	logger.Info("start to list audit log filter rules")

	pager := utils.NewAipPager()
	err = pager.Normalize(request)
	if err != nil {
		return nil, err
	}
	filterRules, total, err := a.auditLogSvc.ListAuditLogFilterRules(ctx, &vo.ListFilterRuleOpts{
		ClusterID: request.ClusterId,
		Limit:     int32(pager.PageSize()),
		Offset:    int64(pager.Offset()),
	})
	if err != nil {
		return nil, err
	}

	// build response
	v1beta1FilteRules := make([]*v1beta1.AuditLogFilterRule, 0, len(filterRules))
	for _, f := range filterRules {
		v1beta1FilteRules = append(v1beta1FilteRules, vo.FilterRuleToV1beta1(f))
	}
	nextPageToken := pager.NextPageToken(total)
	return &v1beta1.ListAuditLogFilterRulesResponse{
		FilterRules:   v1beta1FilteRules,
		NextPageToken: nextPageToken,
		TotalSize:     total,
	}, nil
}

func (a *databaseAuditLogV1beta1Service) CreateAuditLogFilterRule(
	ctx context.Context,
	request *v1beta1.CreateAuditLogFilterRuleRequest,
) (_ *v1beta1.AuditLogFilterRule, err error) {
	ctx, span := v1beta1Tracer.Start(ctx, "databaseAuditLogV1beta1Service.CreateAuditLogFilterRule")
	logger := log.WithContextL(ctx).With(zap.String("cluster_id", request.ClusterId))
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PrepareAuditLogErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("clusterId", request.ClusterId),
	)
	logger.Info("start to create audit log filter rule")

	filterRule, err := vo.V1beta1CreateToFilterRule(request.FilterRule)
	if err != nil {
		logger.Error("build filter rule failed", zap.Error(err))
		return nil, err
	}

	filterRule, err = a.auditLogSvc.CreateAuditLogFilterRule(ctx, request.ClusterId, filterRule)
	if err != nil {
		return nil, err
	}
	return vo.FilterRuleToV1beta1(filterRule), nil
}

func (a *databaseAuditLogV1beta1Service) DeleteAuditLogFilterRule(
	ctx context.Context,
	request *v1beta1.DeleteAuditLogFilterRuleRequest,
) (_ *v1beta1.AuditLogFilterRule, err error) {
	ctx, span := v1beta1Tracer.Start(ctx, "databaseAuditLogV1beta1Service.DeleteAuditLogFilterRule")
	logger := log.WithContextL(ctx).With(zap.String("filter_rule_id", request.FilterRuleId))
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PrepareAuditLogErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("filterRuleId", request.FilterRuleId),
	)
	logger.Info("start to delete audit log filter rule")
	if request.FilterRuleId == "" {
		return nil, bizerr.New(bizerr.ErrCodeParams, "empty filter rule id")
	}
	filterRule, err := a.auditLogSvc.DeleteAuditLogFilterRule(ctx, request.ClusterId, request.FilterRuleId)
	if err != nil {
		return nil, err
	}
	return vo.FilterRuleToV1beta1(filterRule), nil
}

func (a *databaseAuditLogV1beta1Service) UpdateAuditLogFilterRule(
	ctx context.Context,
	request *v1beta1.UpdateAuditLogFilterRuleRequest,
) (_ *v1beta1.AuditLogFilterRule, err error) {
	ctx, span := v1beta1Tracer.Start(ctx, "databaseAuditLogV1beta1Service.UpdateAuditLogFilterRule")
	logger := log.WithContextL(ctx).With(zap.String("filter_rule_id", request.FilterRuleId))
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PrepareAuditLogErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("filterRuleId", request.FilterRuleId),
	)
	logger.Info("start to update audit log filter rule")

	filterRule, err := vo.V1beta1UpdateToFilterRule(request)
	if err != nil {
		logger.Error("build filter rule failed", zap.Error(err))
		return nil, err
	}

	filterRule, err = a.auditLogSvc.UpdateAuditLogFilterRule(ctx, request.ClusterId, filterRule)
	if err != nil {
		return nil, err
	}
	return vo.FilterRuleToV1beta1(filterRule), nil
}

func (a *databaseAuditLogV1beta1Service) GetAuditLogConfig(
	ctx context.Context,
	request *v1beta1.GetAuditLogConfigRequest,
) (_ *v1beta1.AuditLogConfig, err error) {
	ctx, span := v1beta1Tracer.Start(ctx, "databaseAuditLogV1beta1Service.GetAuditLogConfig")
	logger := log.WithContextL(ctx).With(zap.String("cluster_id", request.ClusterId))
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PrepareAuditLogErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("clusterId", request.ClusterId),
	)
	logger.Info("start to get audit log config")

	auditLogConfig, err := a.auditLogSvc.GetAuditLogConfig(ctx, request.ClusterId)
	if err != nil {
		logger.Error("failed to get audit log config", zap.Error(err))
		return nil, err
	}
	return vo.AuditLogConfigToV1beta1(auditLogConfig), nil
}

func (a *databaseAuditLogV1beta1Service) UpdateAuditLogConfig(
	ctx context.Context,
	request *v1beta1.UpdateAuditLogConfigRequest,
) (_ *v1beta1.AuditLogConfig, err error) {
	ctx, span := v1beta1Tracer.Start(ctx, "databaseAuditLogV1beta1Service.UpdateAuditLogConfig")
	logger := log.WithContextL(ctx).With(zap.String("cluster_id", request.ClusterId))
	defer func() {
		tracing.EndSpan(span, err)
		err = errorcode.PrepareAuditLogErrorMessage(ctx, err)
	}()
	span.SetAttributes(
		attribute.String("clusterId", request.ClusterId),
	)
	logger.Info("start to update audit log config")

	auditLogConfig, err := vo.V1beta1UpdateToAuditLogConfig(request.ClusterId, request.AuditLogConfig)
	if err != nil {
		logger.Error("build audit log config failed", zap.Error(err))
		return nil, err
	}

	auditLogConfig, err = a.auditLogSvc.UpdateAuditLogConfig(ctx, auditLogConfig)
	if err != nil {
		logger.Error("failed to update audit log config", zap.Error(err))
		return nil, err
	}

	return vo.AuditLogConfigToV1beta1(auditLogConfig), nil
}
