package auth

import (
	"context"
	"net/mail"
	"strings"

	"github.com/perfect-panel/server/internal/config"
	usermodel "github.com/perfect-panel/server/internal/model/user"
	"gorm.io/gorm"
)

// IsEmailDomainWhitelisted checks if the email's domain is in the comma-separated whitelist.
// Returns false if the email format is invalid.
func IsEmailDomainWhitelisted(email, whitelistCSV string) bool {
	if whitelistCSV == "" {
		return false
	}
	_, domain, ok := parseStrictEmail(email)
	if !ok {
		return false
	}
	for _, d := range strings.Split(whitelistCSV, ",") {
		if strings.ToLower(strings.TrimSpace(d)) == domain {
			return true
		}
	}
	return false
}

func ShouldGrantTrialForEmail(register config.RegisterConfig, email string) bool {
	if !register.EnableTrial {
		return false
	}
	if !IsValidTrialEmail(email) {
		return false
	}
	// 无论白名单是否启用，泛域名邮箱（含 + 别名或 Gmail 点号）始终拒绝赠送
	if IsDisposableAlias(email) {
		return false
	}
	if isConfusableGmailDomain(emailDomain(email)) {
		return false
	}
	if !register.EnableTrialEmailWhitelist {
		return true
	}
	if register.TrialEmailDomainWhitelist == "" {
		return false
	}
	if !IsEmailDomainWhitelisted(email, register.TrialEmailDomainWhitelist) {
		return false
	}
	return true
}

// IsTrialConfigReady verifies that trial auto-grant has all required config.
func IsTrialConfigReady(register config.RegisterConfig) bool {
	return register.EnableTrial &&
		register.TrialSubscribe > 0 &&
		register.TrialTime > 0 &&
		strings.TrimSpace(register.TrialTimeUnit) != ""
}

// ShouldAutoGrantTrialOnPublicEmailFlows defines whether browser/email-originated
// flows may auto-create a trial subscription. Email-specific abuse protection
// is still handled by ShouldGrantTrialForEmail and NormalizedEmailHasTrial.
func ShouldAutoGrantTrialOnPublicEmailFlows(register config.RegisterConfig) bool {
	return IsTrialConfigReady(register)
}

// IsDisposableAlias detects Gmail dot trick and + alias abuse.
// For Gmail-like domains, local part containing "." or "+" is rejected.
// For all other domains, only "+" alias is rejected.
func IsDisposableAlias(email string) bool {
	local, domain, ok := parseStrictEmail(email)
	if !ok {
		return false
	}

	// All domains: reject + alias
	if strings.ContainsRune(local, '+') {
		return true
	}
	// Gmail-like domains: reject dots in local part
	if isGmailLikeDomain(domain) && strings.ContainsRune(local, '.') {
		return true
	}
	return false
}

// NormalizeEmail returns a canonical form of the email for trial deduplication.
// Strips "+" aliases universally (user+tag@any.com → user@any.com).
// Removes dots from local part for Gmail-like providers (gmail.com, googlemail.com).
func NormalizeEmail(email string) string {
	email = strings.ToLower(strings.TrimSpace(email))
	local, domain, ok := parseStrictEmail(email)
	if !ok {
		return email
	}

	// Strip + alias
	if idx := strings.IndexByte(local, '+'); idx != -1 {
		local = local[:idx]
	}

	// Remove dots for Gmail-like providers that ignore dots in local part
	if isGmailLikeDomain(domain) {
		local = strings.ReplaceAll(local, ".", "")
	}

	return local + "@" + domain
}

func isGmailLikeDomain(domain string) bool {
	switch domain {
	case "gmail.com", "googlemail.com":
		return true
	}
	return false
}

func IsValidTrialEmail(email string) bool {
	local, domain, ok := parseStrictEmail(email)
	if !ok {
		return false
	}
	return local != "" && domain != ""
}

func parseStrictEmail(email string) (local, domain string, ok bool) {
	email = strings.ToLower(strings.TrimSpace(email))
	if email == "" || strings.ContainsAny(email, " \t\r\n") {
		return "", "", false
	}
	addr, err := mail.ParseAddress(email)
	if err != nil || addr.Address != email || addr.Name != "" {
		return "", "", false
	}
	parts := strings.Split(addr.Address, "@")
	if len(parts) != 2 {
		return "", "", false
	}
	local = strings.TrimSpace(parts[0])
	domain = strings.Trim(strings.TrimSpace(parts[1]), ".")
	if local == "" || domain == "" || strings.Contains(domain, "..") || !strings.Contains(domain, ".") {
		return "", "", false
	}
	return local, domain, true
}

func emailDomain(email string) string {
	_, domain, ok := parseStrictEmail(email)
	if !ok {
		return ""
	}
	return domain
}

func isConfusableGmailDomain(domain string) bool {
	switch strings.ToLower(strings.TrimSpace(domain)) {
	case "gmaial.com", "gmial.com", "gmai.com", "gamil.com", "gmal.com", "gmail.co", "gmail.con":
		return true
	}
	return false
}

// NormalizedEmailHasTrial returns true if any user with the same normalized email
// already holds a trial subscription. Only performs the cross-user DB check when
// normalization actually changes the email (i.e., dots removed or + alias stripped).
func NormalizedEmailHasTrial(ctx context.Context, db *gorm.DB, email string, trialSubscribeId int64) bool {
	normalized := NormalizeEmail(email)
	if normalized == strings.ToLower(strings.TrimSpace(email)) {
		return false // normalization changed nothing, skip cross-user check
	}

	parts := strings.SplitN(normalized, "@", 2)
	if len(parts) != 2 {
		return false
	}
	domain := parts[1]

	var authMethods []usermodel.AuthMethods
	if err := db.WithContext(ctx).
		Model(&usermodel.AuthMethods{}).
		Select("user_id, auth_identifier").
		Where("auth_type = ? AND auth_identifier LIKE ?", "email", "%@"+domain).
		Find(&authMethods).Error; err != nil {
		return false
	}

	for _, am := range authMethods {
		if NormalizeEmail(am.AuthIdentifier) != normalized {
			continue
		}
		var count int64
		if err := db.WithContext(ctx).
			Model(&usermodel.Subscribe{}).
			Where("user_id = ? AND subscribe_id = ?", am.UserId, trialSubscribeId).
			Count(&count).Error; err != nil {
			continue
		}
		if count > 0 {
			return true
		}
	}
	return false
}
