package log

import (
	"sync"
	"sync/atomic"

	"github.com/pingcap/log"
	"go.uber.org/zap"
)

var (
	auditMutex                         sync.Mutex
	auditLogger, auditLoggerProperties atomic.Value

	AUDIT_LOG_DEFAULT_FILE_PATH     = "/var/log/dbaas/audit.log"
	AUDIT_LOG_DEFAULT_MAX_FILE_SIZE = 512
	AUDIT_LOG_DEFAULT_MAX_BACKUPS   = 8
	AUDIT_LOG_DEFAULT_MAX_DAYS      = 180
)

func init() {
	InitAuditLogger(
		AUDIT_LOG_DEFAULT_FILE_PATH,
		AUDIT_LOG_DEFAULT_MAX_FILE_SIZE,
		AUDIT_LOG_DEFAULT_MAX_BACKUPS,
		AUDIT_LOG_DEFAULT_MAX_DAYS,
	)
}

func InitAuditLogger(filename string, maxSize int, maxBackups int, maxDays int) {
	logger, props, _ := log.InitLogger(&log.Config{Level: "info", File: log.FileLogConfig{
		Filename:   filename,
		MaxSize:    maxSize,
		MaxBackups: maxBackups,
		MaxDays:    maxDays,
	}})

	auditMutex.Lock()
	auditLogger.Store(logger)
	auditLoggerProperties.Store(props)
	auditMutex.Unlock()
}

// audit returns the global Logger. And it's safe for concurrent use.
func audit() *zap.Logger {
	return auditLogger.Load().(*zap.Logger)
}

type AuditEventType string

const (
	AuditEventSignIn                     AuditEventType = "AuditEventSignIn"
	AuditEventSignOut                    AuditEventType = "AuditEventSignOut"
	AuditEventUpdateUserProfile          AuditEventType = "AuditEventUpdateUserProfile"
	AuditEventUpdateMFA                  AuditEventType = "AuditEventUpdateMFA"
	AuditEventCreateProject              AuditEventType = "AuditEventCreateProject"
	AuditEventUpdateProject              AuditEventType = "AuditEventUpdateProject"
	AuditEventDeleteProject              AuditEventType = "AuditEventDeleteProject"
	AuditEventInviteUserIntoProject      AuditEventType = "AuditEventInviteUserIntoProject"
	AuditEventDeleteProjectUser          AuditEventType = "AuditEventDeleteProjectUser"
	AuditEventUpdateOrg                  AuditEventType = "AuditEventUpdateOrg"
	AuditEventCreateIntegration          AuditEventType = "AuditEventCreateIntegration"
	AuditEventDeleteIntegration          AuditEventType = "AuditEventDeleteIntegration"
	AuditEventListOrgUsers               AuditEventType = "AuditEventListOrgUsers"
	AuditEventListProjectUsers           AuditEventType = "AuditEventListProjectUsers"
	AuditEventAddNewPaymentMethod        AuditEventType = "AuditEventAddNewPaymentMethod"
	AuditEventUpdatePaymentMethod        AuditEventType = "AuditEventUpdatePaymentMethod"
	AuditEventDeletePaymentMethod        AuditEventType = "AuditEventDeletePaymentMethod"
	AuditEventCreateAWSVpcPeering        AuditEventType = "AuditEventCreateAWSVpcPeering"
	AuditEventCreateGCPVpcPeering        AuditEventType = "AuditEventCreateGCPVpcPeering"
	AuditEventListAWSVpcPeering          AuditEventType = "AuditEventListAWSVpcPeering"
	AuditEventListGCPVpcPeering          AuditEventType = "AuditEventListGCPVpcPeering"
	AuditEventDeleteAWSVpcPeering        AuditEventType = "AuditEventDeleteAWSVpcPeering"
	AuditEventDeleteGCPVpcPeering        AuditEventType = "AuditEventDeleteGCPVpcPeering"
	AuditEventGetTrafficFilter           AuditEventType = "AuditEventGetTrafficFilter"
	AuditEventUpdateTrafficFilter        AuditEventType = "AuditEventUpdateTrafficFilter"
	AuditEventCreateProjectCIDR          AuditEventType = "AuditEventCreateProjectCIDR"
	AuditEventGetProjectCIDR             AuditEventType = "AuditEventGetProjectCIDR"
	AuditEventGetProjectRegionCIDR       AuditEventType = "AuditEventGetProjectRegionCIDR"
	AuditEventDeleteBackupInRecycleBin   AuditEventType = "AuditEventDeleteBackupInRecycleBin"
	AuditEventChangeClusterRootPassword  AuditEventType = "AuditEventChangeClusterRootPassword"
	AuditEventCreateImportTask           AuditEventType = "AuditEventCreateImportTask"
	AuditEventCancleImportTask           AuditEventType = "AuditEventCancleImportTask"
	AuditEventExitImportTask             AuditEventType = "AuditEventExitImportTask"
	AuditEventCreateCluster              AuditEventType = "AuditEventCreateCluster"
	AuditEventDeleteCluster              AuditEventType = "AuditEventDeleteCluster"
	AuditEventScaleCluster               AuditEventType = "AuditEventScaleCluster"
	AuditEventCreateBackup               AuditEventType = "AuditEventCreateBackup"
	AuditEventDeleteBackup               AuditEventType = "AuditEventDeleteBackup"
	AuditEventRestoreBackup              AuditEventType = "AuditEventRestoreBackup"
	AuditEventUpdateAuditLogStatus       AuditEventType = "AuditEventUpdateAuditLogStatus"
	AuditEventCreateAuditLogAccessRecord AuditEventType = "AuditEventCreateAuditLogAccessRecord"
	AuditEventDeleteAuditLogAccessRecord AuditEventType = "AuditEventDeleteAuditLogAccessRecord"
	AuditEventUpdateUserRole             AuditEventType = "AuditEventUpdateUserRole"
	AuditEventCreateCredit               AuditEventType = "AuditEventCreateCredit"
	AuditEventReclaimCredit              AuditEventType = "AuditEventReclaimCredit"
	AuditEventUpdateDiscount             AuditEventType = "AuditEventUpdateDiscount"
	AuditEventConfirmInvoice             AuditEventType = "AuditEventConfirmInvoice"
)

func Audit(eventType AuditEventType, userID uint64, clientIP string, isSuccess bool, fields ...zap.Field) {
	auditFields := append([]zap.Field{
		zap.Uint64("userID", userID), zap.String("clientIP", clientIP), zap.Bool("isSuccess", isSuccess),
	}, fields...)

	audit().WithOptions(zap.AddCallerSkip(1)).Info(string(eventType), auditFields...)
}

type AuditSession struct {
	eventType AuditEventType
	userID    uint64
	clientIP  string
	isSuccess bool

	fields []zap.Field
}

func NewAuditSession(eventType AuditEventType) *AuditSession {
	return &AuditSession{
		eventType: eventType,
	}
}

func (s *AuditSession) SetUserID(userID uint64) {
	s.userID = userID
}

func (s *AuditSession) SetClientIP(clientIP string) {
	s.clientIP = clientIP
}

func (s *AuditSession) IsSuccess(isSuccess bool) bool {
	s.isSuccess = isSuccess
	return s.isSuccess
}

func (s *AuditSession) AddFields(fields ...zap.Field) {
	s.fields = append(s.fields, fields...)
}

func (s *AuditSession) Submit() {
	auditFields := append([]zap.Field{
		zap.Uint64("userID", s.userID), zap.String("clientIP", s.clientIP), zap.Bool("isSuccess", s.isSuccess),
	}, s.fields...)

	audit().WithOptions(zap.AddCallerSkip(1)).Info(string(s.eventType), auditFields...)
}
