// Package audit_log provides callback service for front-end
package audit_log

import (
	"context"
	"encoding/json"

	auditlog2 "github.com/tidbcloud/account/internel/svc/auditlog"
	user2 "github.com/tidbcloud/account/internel/svc/user"

	"github.com/pingcap/errors"
	"github.com/tidbcloud/account/commons/database"
	audit_event "github.com/tidbcloud/pkgv2/log"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"github.com/tidbcloud/tiinf/sdk/auditlog"
	"go.uber.org/zap"
	"google.golang.org/protobuf/types/known/emptypb"
	"gorm.io/gorm"

	"github.com/tidbcloud/account/idl/pbgen/proto/central"
)

type auditLogService struct {
	central.UnimplementedAuditLogServiceServer
}

var _ central.AuditLogServiceServer = (*auditLogService)(nil)

func NewService() central.AuditLogServiceServer {
	return &auditLogService{}
}

func (s *auditLogService) CreateAuditLogReq(ctx context.Context, req *central.CreateAuditLogReq) (*emptypb.Empty, error) {
	eventType := auditlog.EventType(req.EventType)
	d := auditlog.Details{}
	if err := json.Unmarshal([]byte(req.Details), &d); err != nil {
		return nil, errs.NewBadRequest("invalid details", err)
	}

	var (
		operatorID          uint64 = 0
		operatorLoginMethod        = "auth0"
	)

	if req.OperatorEmail != "" {
		err := database.Transaction(func(tx *gorm.DB) error {
			user, err := user2.GetUserByEmail(tx, req.OperatorEmail)
			if err != nil {
				return err
			}

			operatorID = user.ID.Uint64()

			return err
		})
		if err != nil {
			tilog.Error(ctx, "failed to find user by email", zap.String("email", req.OperatorEmail), zap.Error(err))
		}
	}
	if req.OperatorLoginMethod != "" {
		operatorLoginMethod = req.OperatorLoginMethod
	}

	auditlog.NewEvent(ctx, eventType).SetUserToken(req.UserToken).SetOperatorEmail(req.OperatorEmail).Succeed().
		SetOperatorID(operatorID).SetOperatorLoginMethod(operatorLoginMethod).SetDetails(d.Build()).SetOrgID(req.OrgId).
		SetProjectID(req.ProjectId).SetClusterID(req.ClusterId).Submit() //nolint:errcheck

	return &emptypb.Empty{}, nil
}

func (s *auditLogService) Auth0AuditLog(ctx context.Context, req *central.Auth0AuditLogReq) (*emptypb.Empty, error) {
	/*var auditEvent audit_event.AuditEventType
	switch req.Operation {
	case central.Auth0AuditLogReq_SIGNIN:
		auditEvent = audit_event.AuditEventSignIn
	case central.Auth0AuditLogReq_SIGNOUT:
		auditEvent = audit_event.AuditEventSignOut
	}
	if req.IsSuccess {
		utils.AuditLog(ctx, nil, auditEvent)
	} else {
		utils.AuditLog(ctx, errors.Errorf(req.FailureInfo), auditEvent, zap.String("failureInfo", req.FailureInfo))
	}*/

	user, err := user2.GetUserFromContext(ctx)
	if err != nil {
		return nil, err
	}
	eventType := auditlog.EventLogoutOrg
	if req.Operation == central.Auth0AuditLogReq_SIGNIN {
		eventType = auditlog.EventLoginOrg
	}
	eventBuilder := auditlog.NewEvent(ctx, eventType).
		SetOperatorType(auditlog.OperatorTypeUser).
		SetOperatorEmail(user.Email).
		SetOperatorID(user.ID.Uint64()).
		SetOrgID(req.OrgId).
		SetResult(req.IsSuccess)
	details := auditlog.Details{
		auditlog.KeyUserEmail: user.Email,
		auditlog.KeyError:     nil,
	}
	if !req.IsSuccess {
		details[auditlog.KeyError] = req.FailureInfo
	}
	eventBuilder.SetDetails(details.Build())
	err = eventBuilder.Submit()
	tilog.Info(ctx, "Auth0AuditLog create auditlog result", zap.Any("email", user.Email), zap.Any("org_id", req.OrgId), zap.Any("eventType", eventType), zap.Any("err", err))

	return &emptypb.Empty{}, nil
}

func (s *auditLogService) PaymentMethodAuditLog(ctx context.Context, req *central.PaymentMethodAuditLogReq) (*emptypb.Empty, error) {
	var auditEvent audit_event.AuditEventType
	switch req.Operation {
	case central.PaymentMethodAuditLogReq_ADD:
		auditEvent = audit_event.AuditEventAddNewPaymentMethod
	case central.PaymentMethodAuditLogReq_UPDATE:
		auditEvent = audit_event.AuditEventUpdatePaymentMethod
	case central.PaymentMethodAuditLogReq_DELETE:
		auditEvent = audit_event.AuditEventDeletePaymentMethod
	}
	var err error
	if req.IsSuccess {
		err = nil
	} else {
		err = errors.Errorf("failed to modify payment method")
	}
	auditlog2.AuditLog(ctx, err, auditEvent, zap.String("cardNumber", req.CardNumberLast4))
	return &emptypb.Empty{}, nil
}
