package auditlogsender

import (
	"context"
	"errors"

	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"go.uber.org/zap"

	"github.com/tidbcloud/tidb-management-service/tidb-mgmt-service/domain/adapter/eventbus"
	"github.com/tidbcloud/tidb-management-service/utilities/bizerr"
	"github.com/tidbcloud/tidb-management-service/utilities/grpc/metadata"
)

type consoleAuditLogSenderImpl struct{}

func NewConsoleAuditLogSender() eventbus.ConsoleAuditLogSender {
	return &consoleAuditLogSenderImpl{}
}

func (c *consoleAuditLogSenderImpl) SubmitAuditLogForNextgen(
	ctx context.Context,
	eventType auditlog.EventType,
	reqMeta *metadata.RequestMeta,
	detailFunc func() auditlog.Details,
	rErr error,
) {
	c.submitAuditLog(ctx, eventType, reqMeta, detailFunc, rErr, false)
}

func (c *consoleAuditLogSenderImpl) SubmitAuditLog(
	ctx context.Context,
	eventType auditlog.EventType,
	reqMeta *metadata.RequestMeta,
	detailFunc func() auditlog.Details,
	rErr error,
) {
	c.submitAuditLog(ctx, eventType, reqMeta, detailFunc, rErr, true)
}

func (c *consoleAuditLogSenderImpl) submitAuditLog(
	ctx context.Context,
	eventType auditlog.EventType,
	reqMeta *metadata.RequestMeta,
	detailFunc func() auditlog.Details,
	rErr error,
	withProject bool,
) {
	if reqMeta == nil {
		tilog.Warn(
			ctx,
			"failed to submit console audit log",
			zap.String("failed", "request metadata is nil"),
			zap.Error(rErr),
		)
		return
	}

	details := make(auditlog.Details)
	if detailFunc != nil {
		details = detailFunc()
	}

	eventBuilder := auditlog.NewEvent(ctx, eventType).SetResult(rErr == nil).SetOrgID(reqMeta.OrgID)
	if withProject && reqMeta.DefaultProjectID != 0 {
		eventBuilder.SetProjectID(reqMeta.DefaultProjectID)
	}

	if reqMeta.ClusterID != 0 {
		eventBuilder.SetClusterID(reqMeta.ClusterID)
	}

	if rErr != nil {
		details[auditlog.KeyError] = wrapAuditError(rErr)
	}

	eventBuilder.SetDetails(details.Build())

	// The kafka broker address is set in one-config sdk.
	// One-config sdk will get broker address from the configmap in cm namespace.
	//
	// This behavior requires the program to be deployed in the same k8s and have access to the cm namespace.

	err := eventBuilder.Submit()
	if err != nil {
		tilog.Warn(ctx, "failed to submit console audit log", zap.Error(err))
	}
}

func wrapAuditError(err error) error {
	if err == nil {
		return nil
	}

	var be bizerr.BizError
	if ok := errors.As(err, &be); ok {
		code := be.ErrCode()
		msg := be.ErrMsg()
		if bizerr.IsRequestErr(code) || bizerr.IsBusinessErr(code) {
			return errors.New(msg)
		}
		return errors.New("internal error")
	}

	return err
}
