package storage

import (
	"crypto/rand"
	"errors"
	"regexp"
	"strings"
	"time"

	"gorm.io/gorm"
	"gorm.io/gorm/clause"

	"sproutgate-backend/internal/models"
)

const (
	defaultForbiddenAccountsCSV        = "sb,mail,nmsl,cnmb,smy,shumengya"
	defaultInviteRegisterRewardCoins   = 10
	// MinSelfServiceAccountLen / MaxSelfServiceAccountLen 自助注册账号长度（仅小写与数字）。
	MinSelfServiceAccountLen = 3
	MaxSelfServiceAccountLen = 32
)

func effectiveInviteRegisterRewardCoins(stored *int) int {
	if stored == nil {
		return defaultInviteRegisterRewardCoins
	}
	if *stored < 0 {
		return 0
	}
	return *stored
}

var selfServiceAccountPattern = regexp.MustCompile(`^[a-z0-9]+$`)

// NormalizeSelfServiceAccount 自助注册账号：去空白并转为小写。
func NormalizeSelfServiceAccount(raw string) string {
	return strings.ToLower(strings.TrimSpace(raw))
}

func effectiveForbiddenAccountsCSV(raw string) string {
	if strings.TrimSpace(raw) == "" {
		return defaultForbiddenAccountsCSV
	}
	return strings.TrimSpace(raw)
}

func parseForbiddenAccountSet(csv string) map[string]struct{} {
	out := make(map[string]struct{})
	for _, part := range strings.Split(csv, ",") {
		t := strings.ToLower(strings.TrimSpace(part))
		if t == "" {
			continue
		}
		out[t] = struct{}{}
	}
	return out
}

// InviteEntry 管理员发放的注册邀请码。
type InviteEntry struct {
	Code      string `json:"code"`
	Note      string `json:"note,omitempty"`
	MaxUses   int    `json:"maxUses"` // 0 表示不限次数
	Uses      int    `json:"uses"`
	ExpiresAt string `json:"expiresAt,omitempty"` // RFC3339，空表示不过期
	CreatedAt string `json:"createdAt"`
}

// RegistrationConfig 注册策略（不含邀请码列表，邀请码单独存入 invite_codes 表）。
type RegistrationConfig struct {
	RequireInviteCode           bool          `json:"requireInviteCode"`
	ForbiddenAccounts           string        `json:"forbiddenAccounts"` // 逗号分隔；空串表示使用内置默认禁注列表
	InviteRegisterRewardCoins   int           `json:"inviteRegisterRewardCoins"` // 使用邀请码完成注册时赠送（生效值，默认 10）
	Invites                     []InviteEntry `json:"invites"` // 内存缓存，非 DB 字段
}

func normalizeInviteCode(raw string) string {
	return strings.ToUpper(strings.TrimSpace(raw))
}

type dbRegistrationPolicy struct {
	RequireInviteCode           bool `json:"requireInviteCode"`
	ForbiddenAccounts           string `json:"forbiddenAccounts"`
	InviteRegisterRewardCoins   *int `json:"inviteRegisterRewardCoins,omitempty"` // nil 表示未配置，按内置默认 10
}

func (s *Store) loadOrCreateRegistrationConfig() error {
	var policy dbRegistrationPolicy
	found, err := s.getConfig("registration", &policy)
	if err != nil {
		return err
	}
	if !found {
		policy = dbRegistrationPolicy{RequireInviteCode: false, ForbiddenAccounts: ""}
		if err := s.setConfig("registration", policy); err != nil {
			return err
		}
	}

	// 从 invite_codes 表加载所有邀请码
	invites, err := s.loadAllInvites()
	if err != nil {
		return err
	}

	rewardEff := effectiveInviteRegisterRewardCoins(policy.InviteRegisterRewardCoins)
	s.mu.Lock()
	s.registrationConfig = RegistrationConfig{
		RequireInviteCode:         policy.RequireInviteCode,
		ForbiddenAccounts:         policy.ForbiddenAccounts,
		InviteRegisterRewardCoins: rewardEff,
		Invites:                   invites,
	}
	s.mu.Unlock()
	return nil
}

func (s *Store) loadAllInvites() ([]InviteEntry, error) {
	var rows []DBInviteCode
	if err := s.db.Order("created_at ASC").Find(&rows).Error; err != nil {
		return nil, err
	}
	entries := make([]InviteEntry, 0, len(rows))
	for _, r := range rows {
		entries = append(entries, r.toEntry())
	}
	return entries, nil
}

// RegistrationRequireInvite 是否强制邀请码。
func (s *Store) RegistrationRequireInvite() bool {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.registrationConfig.RequireInviteCode
}

// RegistrationInviteRegisterRewardCoins 使用邀请码完成邮箱验证注册时赠送的萌芽币（未配置时为 10）。
func (s *Store) RegistrationInviteRegisterRewardCoins() int {
	s.mu.RLock()
	defer s.mu.RUnlock()
	return s.registrationConfig.InviteRegisterRewardCoins
}

// GetRegistrationConfig 返回配置副本（含邀请码列表）。
func (s *Store) GetRegistrationConfig() RegistrationConfig {
	s.mu.RLock()
	defer s.mu.RUnlock()
	out := effectiveRegistrationConfigForAPI(s.registrationConfig)
	return out
}

// effectiveRegistrationConfigForAPI 管理端展示用：禁注列表空时透出当前生效的内置默认文案。
func effectiveRegistrationConfigForAPI(in RegistrationConfig) RegistrationConfig {
	out := RegistrationConfig{
		RequireInviteCode:         in.RequireInviteCode,
		ForbiddenAccounts:         in.ForbiddenAccounts,
		InviteRegisterRewardCoins: in.InviteRegisterRewardCoins,
		Invites:                   append([]InviteEntry(nil), in.Invites...),
	}
	if strings.TrimSpace(out.ForbiddenAccounts) == "" {
		out.ForbiddenAccounts = defaultForbiddenAccountsCSV
	}
	return out
}

// ValidateSelfServiceAccount 自助注册账号：仅小写字母与数字，长度与禁注表（可配置，空则用内置默认）。
func (s *Store) ValidateSelfServiceAccount(account string) error {
	acc := NormalizeSelfServiceAccount(account)
	if acc == "" {
		return errors.New("account is required")
	}
	if len(acc) < MinSelfServiceAccountLen || len(acc) > MaxSelfServiceAccountLen {
		return errors.New("account must be 3-32 characters")
	}
	if !selfServiceAccountPattern.MatchString(acc) {
		return errors.New("account may only contain lowercase letters and digits")
	}
	s.mu.RLock()
	csv := s.registrationConfig.ForbiddenAccounts
	s.mu.RUnlock()
	blocked := parseForbiddenAccountSet(effectiveForbiddenAccountsCSV(csv))
	if _, ok := blocked[acc]; ok {
		return errors.New("this account name is not allowed")
	}
	return nil
}

// UpdateRegistrationPolicy 更新注册策略；inviteRewardCoins 为 nil 时不改库中该项（兼容旧客户端）。
func (s *Store) UpdateRegistrationPolicy(require bool, forbiddenAccounts string, inviteRewardCoins *int) error {
	forbiddenAccounts = strings.TrimSpace(forbiddenAccounts)
	var policy dbRegistrationPolicy
	found, err := s.getConfig("registration", &policy)
	if err != nil {
		return err
	}
	if !found {
		policy = dbRegistrationPolicy{}
	}
	policy.RequireInviteCode = require
	policy.ForbiddenAccounts = forbiddenAccounts
	if inviteRewardCoins != nil {
		v := *inviteRewardCoins
		if v < 0 {
			v = 0
		}
		policy.InviteRegisterRewardCoins = &v
	}
	if err := s.setConfig("registration", policy); err != nil {
		return err
	}
	rewardEff := effectiveInviteRegisterRewardCoins(policy.InviteRegisterRewardCoins)
	s.mu.Lock()
	s.registrationConfig.RequireInviteCode = require
	s.registrationConfig.ForbiddenAccounts = forbiddenAccounts
	s.registrationConfig.InviteRegisterRewardCoins = rewardEff
	s.mu.Unlock()
	return nil
}

func inviteEntryValid(e *InviteEntry) error {
	if strings.TrimSpace(e.ExpiresAt) != "" {
		t, err := time.Parse(time.RFC3339, e.ExpiresAt)
		if err == nil && time.Now().After(t) {
			return errors.New("invite code expired")
		}
	}
	if e.MaxUses > 0 && e.Uses >= e.MaxUses {
		return errors.New("invite code has been fully used")
	}
	return nil
}

// ValidateInviteForRegister 校验邀请码是否可用（不扣次）。
func (s *Store) ValidateInviteForRegister(code string) error {
	n := normalizeInviteCode(code)
	if n == "" {
		return errors.New("invite code is required")
	}
	s.mu.RLock()
	defer s.mu.RUnlock()
	for i := range s.registrationConfig.Invites {
		e := &s.registrationConfig.Invites[i]
		if strings.EqualFold(e.Code, n) {
			return inviteEntryValid(e)
		}
	}
	return errors.New("invalid invite code")
}

// RedeemInvite 邮箱验证通过后扣减邀请码使用次数。
func (s *Store) RedeemInvite(code string) error {
	n := normalizeInviteCode(code)
	if n == "" {
		return nil
	}

	s.mu.Lock()
	defer s.mu.Unlock()

	for i := range s.registrationConfig.Invites {
		e := &s.registrationConfig.Invites[i]
		if strings.EqualFold(e.Code, n) {
			if err := inviteEntryValid(e); err != nil {
				return err
			}
			e.Uses++
			// 写回数据库
			return s.db.Model(&DBInviteCode{}).
				Where("code = ?", e.Code).
				Update("uses", e.Uses).Error
		}
	}
	return errors.New("invalid invite code")
}

const inviteCodeAlphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"

func randomInviteToken(n int) (string, error) {
	b := make([]byte, n)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	var sb strings.Builder
	sb.Grow(n)
	for i := 0; i < n; i++ {
		sb.WriteByte(inviteCodeAlphabet[int(b[i])%len(inviteCodeAlphabet)])
	}
	return sb.String(), nil
}

// AddInviteEntry 生成新邀请码并写入数据库。
func (s *Store) AddInviteEntry(note string, maxUses int, expiresAt string) (InviteEntry, error) {
	s.mu.Lock()
	defer s.mu.Unlock()

	var code string
	for attempt := 0; attempt < 24; attempt++ {
		c, err := randomInviteToken(8)
		if err != nil {
			return InviteEntry{}, err
		}
		dup := false
		for _, ex := range s.registrationConfig.Invites {
			if strings.EqualFold(ex.Code, c) {
				dup = true
				break
			}
		}
		if !dup {
			code = c
			break
		}
	}
	if code == "" {
		return InviteEntry{}, errors.New("failed to generate unique invite code")
	}

	expiresAt = strings.TrimSpace(expiresAt)
	if expiresAt != "" {
		if _, err := time.Parse(time.RFC3339, expiresAt); err != nil {
			return InviteEntry{}, errors.New("invalid expiresAt (use RFC3339)")
		}
	}
	if maxUses < 0 {
		maxUses = 0
	}

	entry := InviteEntry{
		Code:      code,
		Note:      strings.TrimSpace(note),
		MaxUses:   maxUses,
		Uses:      0,
		ExpiresAt: expiresAt,
		CreatedAt: models.NowISO(),
	}

	row := dbInviteFromEntry(entry)
	if err := s.db.Clauses(clause.OnConflict{DoNothing: true}).Create(&row).Error; err != nil {
		return InviteEntry{}, err
	}

	s.registrationConfig.Invites = append(s.registrationConfig.Invites, entry)
	return entry, nil
}

// DeleteInviteEntry 按码删除（大小写不敏感）。
func (s *Store) DeleteInviteEntry(code string) error {
	n := normalizeInviteCode(code)
	if n == "" {
		return errors.New("code is required")
	}

	result := s.db.Where("UPPER(code) = ?", n).Delete(&DBInviteCode{})
	if result.Error != nil {
		return result.Error
	}
	if result.RowsAffected == 0 {
		return errors.New("invite not found")
	}

	s.mu.Lock()
	for i, e := range s.registrationConfig.Invites {
		if strings.EqualFold(e.Code, n) {
			s.registrationConfig.Invites = append(
				s.registrationConfig.Invites[:i],
				s.registrationConfig.Invites[i+1:]...,
			)
			break
		}
	}
	s.mu.Unlock()
	return nil
}

// refreshInviteCache 重新从数据库加载邀请码列表（内部用）。
func (s *Store) refreshInviteCache() error {
	invites, err := s.loadAllInvites()
	if err != nil {
		return err
	}
	s.mu.Lock()
	s.registrationConfig.Invites = invites
	s.mu.Unlock()
	return nil
}

// 确保 gorm 包被使用
var _ = gorm.ErrRecordNotFound
