package model

import (
	"time"

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

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 MergeDiscount(checkTime time.Time, globalDiscounts []entity.GlobalDiscount, tenantDiscounts []entity.Discount) (bool, float64, string, int64, int64) {
	hasDiscount := false
	var discount = 1.0
	discountDescription := ""
	var startTime, endTime int64
	monthNow := getTotalMonth(checkTime)
	for _, item := range globalDiscounts {
		if getTotalMonth(item.StartTime) <= monthNow && getTotalMonth(item.EndTime) >= monthNow {
			if discount > item.Discount {
				hasDiscount = true
				discount = item.Discount
				discountDescription = item.Description
				startTime = item.StartTime.Unix()
				endTime = item.EndTime.Unix()
			}
		}
	}
	for _, item := range tenantDiscounts {
		if getTotalMonth(item.StartTime) <= monthNow && getTotalMonth(item.EndTime) >= monthNow {
			if discount > item.Discount {
				hasDiscount = true
				discount = item.Discount
				discountDescription = item.Description
				startTime = item.StartTime.Unix()
				endTime = item.EndTime.Unix()
			}
		}
	}
	if discount >= 1 || discount <= 0 {
		return false, 1, "", 0, 0
	}
	return hasDiscount, discount, discountDescription, startTime, endTime
}

func getTotalMonth(t time.Time) int {
	return t.Year()*12 + int(t.Month())
}

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 GetNodeUsageFromDB(db *gorm.DB, tenantID entity.ID) (int64, error) {
	var nodeUsage entity.NodeUsage
	err := entity.NewNodeUsageQuerySet(db).TenantIDEq(tenantID).One(&nodeUsage)
	if err != nil {
		return 0, err
	}
	return nodeUsage.Usage, nil
}

func InsertCode(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("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(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("failed to validate the code", zap.String("key", key), zap.Error(err))
		return false
	}
	return isValidCode
}
