package auth

import (
	"chat/channel"
	"chat/globals"
	"chat/utils"
	"database/sql"
	"errors"
	"fmt"
	"github.com/go-redis/redis/v8"
	"math"
	"time"
)

func disableSubscription() bool {
	return !channel.PlanInstance.IsEnabled()
}

func (u *User) GetSubscription(db *sql.DB) (time.Time, int) {
	if u.Subscription != nil && u.Subscription.Unix() > 0 {
		return *u.Subscription, u.Level
	}

	var expiredAt []uint8
	if err := globals.QueryRowDb(db, "SELECT expired_at, level FROM subscription WHERE user_id = ?", u.GetID(db)).Scan(&expiredAt, &u.Level); err != nil {
		return time.Unix(0, 0), 0
	}

	t := utils.ConvertTime(expiredAt)
	if t == nil {
		t = utils.ToPtr(time.Unix(0, 0))
	}

	u.Subscription = t
	return *u.Subscription, u.Level
}

func (u *User) GetSubscriptionLevel(db *sql.DB) int {
	_, level := u.GetSubscription(db)
	if !u.IsSubscribe(db) {
		return 0
	}
	return level
}

func (u *User) GetPlan(db *sql.DB) channel.Plan {
	return channel.PlanInstance.GetPlan(u.GetSubscriptionLevel(db))
}

func (u *User) GetSubscriptionExpiredAt(db *sql.DB) time.Time {
	stamp, _ := u.GetSubscription(db)
	return stamp
}

func (u *User) GetSubscriptionTime(db *sql.DB) time.Time {
	stamp, _ := u.GetSubscription(db)
	return stamp
}

func (u *User) IsSubscribe(db *sql.DB) bool {
	stamp, level := u.GetSubscription(db)
	return stamp.Unix() > time.Now().Unix() && level > 0
}

func (u *User) IsEnterprise(db *sql.DB) bool {
	if !u.IsSubscribe(db) {
		return false
	}

	var enterprise sql.NullBool
	if err := globals.QueryRowDb(db, "SELECT enterprise FROM subscription WHERE user_id = ?", u.GetID(db)).Scan(&enterprise); err != nil {
		return false
	}

	return enterprise.Valid && enterprise.Bool
}

func (u *User) GetSubscriptionExpiredDay(db *sql.DB) int {
	stamp := u.GetSubscriptionTime(db).Sub(time.Now())
	return int(math.Round(stamp.Hours() / 24))
}

func (u *User) AddSubscription(db *sql.DB, month int, level int) bool {
	current := u.GetSubscriptionTime(db)
	if current.Unix() < time.Now().Unix() {
		current = time.Now()
	}
	expiredAt := current.AddDate(0, month, 0)
	date := utils.ConvertSqlTime(expiredAt)
	_, err := globals.ExecDb(db, `
		INSERT INTO subscription (user_id, expired_at, total_month, level) VALUES (?, ?, ?, ?)
		ON DUPLICATE KEY UPDATE expired_at = ?, total_month = total_month + ?, level = ?
	`, u.GetID(db), date, month, level, date, month, level)
	return err == nil
}

func (u *User) DowngradePlan(db *sql.DB, target int) error {
	expired, current := u.GetSubscription(db)
	if current == 0 || current == target {
		return fmt.Errorf("invalid plan level")
	}

	now := time.Now()
	weight := channel.PlanInstance.GetPlan(current).Price / channel.PlanInstance.GetPlan(target).Price
	stamp := float32(expired.Unix()-now.Unix()) * weight

	// ceil expired time
	expiredAt := now.Add(time.Duration(stamp)*time.Second).AddDate(0, 0, -1)
	date := utils.ConvertSqlTime(expiredAt)
	_, err := globals.ExecDb(db, "UPDATE subscription SET level = ?, expired_at = ? WHERE user_id = ?", target, date, u.GetID(db))

	return err
}

func (u *User) CountUpgradePrice(db *sql.DB, target int) float32 {
	expired := u.GetSubscriptionExpiredAt(db)
	weight := channel.PlanInstance.GetPlan(target).Price - u.GetPlan(db).Price
	if weight < 0 {
		return 0
	}

	days := expired.Sub(time.Now()).Hours() / 24
	return float32(days) * weight / 30
}

func (u *User) SetSubscriptionLevel(db *sql.DB, level int) bool {
	_, err := globals.ExecDb(db, "UPDATE subscription SET level = ? WHERE user_id = ?", level, u.GetID(db))
	return err == nil
}

func CountSubscriptionPrize(level int, month int) float32 {
	plan := channel.PlanInstance.GetPlan(level)
	base := plan.Price * float32(month)
	if month >= 36 {
		return base * 0.7
	} else if month >= 12 {
		return base * 0.8
	} else if month >= 6 {
		return base * 0.9
	}
	return base
}

func BuySubscription(db *sql.DB, cache *redis.Client, user *User, level int, month int) error {
	if disableSubscription() {
		return errors.New("subscription feature does not enable of this site")
	}

	if month < 1 || month > 999 || !channel.IsValidPlan(level) {
		return errors.New("invalid subscription params")
	}

	before := user.GetSubscriptionLevel(db)
	if before == 0 || before == level {
		// buy new subscription or renew subscription
		money := CountSubscriptionPrize(level, month)
		if user.Pay(db, cache, money) {
			// migrate subscription
			user.AddSubscription(db, month, level)

			if before == 0 {
				// new subscription

				plan := user.GetPlan(db)
				for _, usage := range plan.Items {
					// create usage
					usage.CreateUsage(user, cache)
				}
			}

			return nil
		}
	} else if before > level {
		// downgrade subscription
		return user.DowngradePlan(db, level)
	} else {
		// upgrade subscription
		money := user.CountUpgradePrice(db, level)
		if user.Pay(db, cache, money) {
			user.SetSubscriptionLevel(db, level)
			return nil
		}
	}

	return errors.New("not enough money")
}

func HandleSubscriptionUsage(db *sql.DB, cache *redis.Client, user *User, model string) bool {
	if disableSubscription() {
		return false
	}
	plan := user.GetPlan(db)
	return plan.IncreaseUsage(user, cache, model)
}

func RevertSubscriptionUsage(db *sql.DB, cache *redis.Client, user *User, model string) bool {
	if disableSubscription() {
		return false
	}
	plan := user.GetPlan(db)
	return plan.DecreaseUsage(user, cache, model)
}
