package usecase

import (
	"crypto/rand"
	"math/big"
	"unicode"

	"apps/backend/internal/module/member/domain"

	"golang.org/x/crypto/bcrypt"
)

// ValidatePasswordPolicy：≥12、含大寫、小寫、數字、符號。
// 規則與 apps/web/src/lib/passwordPolicy.ts 一致；文案見 domain.PasswordPolicyEN/ZH。
func ValidatePasswordPolicy(password string) error {
	if password == "" || len([]rune(password)) < domain.PasswordMinLen {
		return domain.ErrWeakPassword
	}
	var upper, lower, digit, symbol bool
	for _, r := range password {
		switch {
		case unicode.IsUpper(r):
			upper = true
		case unicode.IsLower(r):
			lower = true
		case unicode.IsDigit(r):
			digit = true
		case unicode.IsPunct(r) || unicode.IsSymbol(r):
			symbol = true
		default:
			// 空白等非字母數字也算符號類
			if !unicode.IsLetter(r) && !unicode.IsDigit(r) {
				symbol = true
			}
		}
	}
	if !upper || !lower || !digit || !symbol {
		return domain.ErrWeakPassword
	}
	return nil
}

// HashPassword validates policy then bcrypt.
func HashPassword(password string, cost int) (string, error) {
	if err := ValidatePasswordPolicy(password); err != nil {
		return "", err
	}
	if cost < bcrypt.MinCost {
		cost = bcrypt.DefaultCost
	}
	bytes, err := bcrypt.GenerateFromPassword([]byte(password), cost)
	return string(bytes), err
}

func CheckPasswordHash(password, hash string) bool {
	return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil
}

func GetHashingCost(hashedPassword []byte) int {
	cost, _ := bcrypt.Cost(hashedPassword)
	return cost
}

// GenerateTempPassword 產生符合政策的臨時密碼（16 字元）。
func GenerateTempPassword() string {
	const (
		lower   = "abcdefghijkmnopqrstuvwxyz"
		upper   = "ABCDEFGHJKLMNPQRSTUVWXYZ"
		digits  = "23456789"
		symbols = "!@#$%^&*+-="
		all     = lower + upper + digits + symbols
	)
	pick := func(set string) byte {
		n, err := rand.Int(rand.Reader, big.NewInt(int64(len(set))))
		if err != nil {
			return set[0]
		}
		return set[n.Int64()]
	}
	// 保證四類各至少 1
	out := []byte{
		pick(lower), pick(upper), pick(digits), pick(symbols),
	}
	// 16 ≥ PasswordMinLen(12)
	for len(out) < 16 {
		out = append(out, pick(all))
	}
	// shuffle
	for i := len(out) - 1; i > 0; i-- {
		jBig, err := rand.Int(rand.Reader, big.NewInt(int64(i+1)))
		j := 0
		if err == nil {
			j = int(jBig.Int64())
		}
		out[i], out[j] = out[j], out[i]
	}
	return string(out)
}
