package model

import (
	"context"
	"time"

	"github.com/tidbcloud/metrics-server/central/db"
	"github.com/tidbcloud/metrics-server/central/db/dbutil"
	"github.com/tidbcloud/metrics-server/central/db/entity"
	"github.com/tidbcloud/pkgv2/log"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

const (
	ClusterNameUniqueErrMsg = "The cluster name must be unique in an organization."
)

type AccessLog struct {
	ID         uint64
	HappenedOn int64
	User       string
	Content    string
}

type ListAccessLogQuery struct {
	StartTimeInSec int64
	EndTimeInSec   int64
	User           string
	Content        string
	From           int
	Size           int
}

func ListAuditLogs(tenantID entity.ID, query *ListAccessLogQuery) ([]AccessLog, int64, error) {
	var (
		items []entity.AuditLog
		count int64
	)
	err := db.Transaction(func(tx *gorm.DB) error {
		var err error
		items, count, err = dbutil.ListAccessLog(tx, tenantID, &dbutil.ListAccessLogQuery{
			StartTimeInSec: query.StartTimeInSec,
			EndTimeInSec:   query.EndTimeInSec,
			User:           query.User,
			Content:        query.Content,
			From:           query.From,
			Size:           query.Size,
		})
		return err
	})
	if err != nil {
		return nil, 0, err
	}

	var ret []AccessLog
	for _, item := range items {
		ret = append(ret, AccessLog{
			ID:         item.ID.Uint64(),
			HappenedOn: item.CreatedAt.Unix(),
			User:       item.User,
			Content:    item.Content,
		})
	}
	return ret, count, nil
}

func GetStartIndex(page int, itemsPerPage int) int {
	if page <= 0 {
		page = 1
	}
	return (page - 1) * GetSize(itemsPerPage)
}

func GetSize(itemsPerPage int) int {
	if itemsPerPage <= 0 {
		itemsPerPage = 10
	}
	return itemsPerPage
}

func LockTenant(tx *gorm.DB, tenantID entity.ID) {
	var tenant entity.Tenant
	tx.Clauses(clause.Locking{Strength: "UPDATE"}).Model(&entity.Tenant{}).Where("id=?", tenantID.Uint64()).Scan(&tenant)
}

func InsertCode(ctx context.Context, key, randCode string) error {
	return db.Transaction(func(tx *gorm.DB) error {
		var oldRecords []entity.VerifyCode
		if err := entity.NewVerifyCodeQuerySet(tx).CodeKeyEq(key).All(&oldRecords); err != nil {
			return err
		}
		if len(oldRecords) > 0 {
			for _, item := range oldRecords {
				if err := item.Delete(tx); err != nil {
					return err
				}
				log.Info(ctx, "delete verify code", zap.String("key", key))
			}
		}
		return tx.Create(&entity.VerifyCode{
			CodeKey:    key,
			ExpireTime: time.Now().Add(48 * time.Hour),
			Code:       randCode,
		}).Error
	})
}

func ValidateCode(ctx context.Context, key, code string) bool {
	var isValidCode = false
	err := db.Transaction(func(tx *gorm.DB) error {
		var dbRecords []entity.VerifyCode
		if err := entity.NewVerifyCodeQuerySet(tx).CodeKeyEq(key).All(&dbRecords); err != nil {
			return err
		}
		if len(dbRecords) > 0 {
			if dbRecords[0].Code == code && dbRecords[0].ExpireTime.After(time.Now()) {
				isValidCode = true
				//ok let's delete it
				return dbRecords[0].Delete(tx)
			}
		}
		return nil
	})
	if err != nil {
		log.Warn(ctx, "failed to validate the code", zap.String("key", key), zap.Error(err))
		return false
	}
	return isValidCode
}
