package dto

import (
	"context"
	"encoding/json"
	"strings"

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

	"github.com/tidbcloud/auditlog/consts"
	"github.com/tidbcloud/auditlog/internal/entity"
	"github.com/tidbcloud/auditlog/types"
)

var (
	keysToDeleteMapper = map[string]bool{}
	keysToMarkMapper   = map[string]bool{
		auditlog.KeyPhoneNumber: true,
		auditlog.KeyAddress1:    true,
		auditlog.KeyAddress2:    true,
		auditlog.KeyCardNumber:  true,
		auditlog.KeyCvcOrCvv:    true,
	}
)

func markingValue(v string) string {
	var sb strings.Builder

	for _, i := range v {
		if i >= '0' && i <= '9' {
			sb.WriteRune('?')
		} else {
			sb.WriteRune(i)
		}
	}

	return sb.String()
}

func masking(ctx context.Context, origin string) (string, error) {
	m := make(map[string]string)
	if err := json.Unmarshal([]byte(origin), &m); err != nil {
		return "", err
	}

	for k, v := range m {
		if keysToDeleteMapper[k] {
			delete(m, k)
			tilog.Info(ctx, "masking: delete", zap.String("key", k))
		} else if keysToMarkMapper[k] {
			m[k] = markingValue(v)
			tilog.Info(ctx, "masking: replace", zap.String("key", k))
		}
	}

	content, err := json.Marshal(&m)
	if err != nil {
		return "", err
	}

	return string(content), nil
}

func ConvertToDBEvent(e *auditlog.Event) *entity.Event {
	ctx := context.TODO()
	if len(e.Details) > 0 {
		var err error
		e.Details, err = masking(ctx, e.Details)
		if err != nil {
			tilog.Error(ctx, "failed to mask message, ignore the details part", zap.Error(err), zap.String("details", e.Details))
			return nil
		}
	}

	return &entity.Event{
		Type:                e.Type,
		EndsAt:              e.EndsAt,
		OperatorType:        e.OperatorType,
		OperatorID:          e.OperatorID,
		OperatorEmail:       e.OperatorEmail,
		OperatorIP:          e.OperatorIP,
		OperatorLoginMethod: e.OperatorLoginMethod,
		OrgID:               e.OrgID,
		OrgName:             e.OrgName,
		ProjectID:           e.ProjectID,
		ProjectName:         e.ProjectName,
		ClusterID:           e.ClusterID,
		ClusterName:         e.ClusterName,
		TraceID:             e.TraceID,
		Result:              e.Result,
		Details:             e.Details,
		ClusterType:         e.ClusterType,
	}
}

func ConvertToDBInternalEvent(e *auditlog.Event) *entity.InternalEvent {
	ctx := context.TODO()
	if len(e.Details) > 0 {
		var err error
		e.Details, err = masking(ctx, e.Details)
		if err != nil {
			tilog.Error(ctx, "failed to mask message, ignore the details part", zap.Error(err), zap.String("details", e.Details))
			return nil
		}
	}

	return &entity.InternalEvent{
		Type:                e.Type,
		EndsAt:              e.EndsAt,
		OperatorType:        e.OperatorType,
		OperatorID:          e.OperatorID,
		OperatorEmail:       e.OperatorEmail,
		OperatorIP:          e.OperatorIP,
		OperatorLoginMethod: e.OperatorLoginMethod,
		OrgID:               e.OrgID,
		OrgName:             e.OrgName,
		ProjectID:           e.ProjectID,
		ProjectName:         e.ProjectName,
		ClusterID:           e.ClusterID,
		ClusterName:         e.ClusterName,
		TraceID:             e.TraceID,
		Result:              e.Result,
		Details:             e.Details,
		ClusterType:         e.ClusterType,
	}
}

func FormatEvent(e entity.Event) types.ExportEvent {
	traceID := "-"
	if e.TraceID != "" {
		traceID = e.TraceID
	}
	return types.ExportEvent{
		Type:                consts.GetEventTypeName(e.Type),
		EndsAt:              e.EndsAt,
		OperatorType:        consts.GetOperatorTypeName(e.OperatorType),
		OperatorID:          e.OperatorID,
		OperatorEmail:       e.OperatorEmail,
		OperatorIP:          e.OperatorIP,
		OperatorLoginMethod: e.OperatorLoginMethod,
		OrgID:               e.OrgID,
		OrgName:             e.OrgName,
		ProjectID:           e.ProjectID,
		ProjectName:         e.ProjectName,
		ClusterID:           e.ClusterID,
		ClusterName:         e.ClusterName,
		TraceID:             traceID,
		Result:              consts.GetResultName(e.Result),
		Details:             e.Details,
		ClusterType:         consts.GetClusterTypeName(e.ClusterType),
	}
}
