package smsstore

import (
	"crypto/rand"
	"fmt"
	"sync"
	"time"
)

type Purpose string

const (
	PurposeLogin Purpose = "login"
	PurposeBind  Purpose = "bind"
)

type Record struct {
	Code      string
	ExpiresAt time.Time
	SentAt    time.Time
	Attempts  int
}

type Store struct {
	mu          sync.Mutex
	byKey       map[string]*Record
	ttl         time.Duration
	resendGap   time.Duration
	maxAttempts int
	fixedCode   string // 开发固定码；空则随机 6 位
}

func New(ttl, resendGap time.Duration, fixedCode string) *Store {
	if ttl <= 0 {
		ttl = 5 * time.Minute
	}
	if resendGap <= 0 {
		resendGap = 60 * time.Second
	}
	return &Store{
		byKey:       map[string]*Record{},
		ttl:         ttl,
		resendGap:   resendGap,
		maxAttempts: 5,
		fixedCode:   fixedCode,
	}
}

func key(purpose Purpose, phone string) string {
	return string(purpose) + ":" + phone
}

// Issue 生成并保存验证码；若未到重发间隔返回 retryAfter>0。
func (s *Store) Issue(purpose Purpose, phone string) (code string, expiresIn, retryAfter int, err error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.gcLocked()
	k := key(purpose, phone)
	now := time.Now()
	if old, ok := s.byKey[k]; ok && now.Sub(old.SentAt) < s.resendGap {
		left := int((s.resendGap - now.Sub(old.SentAt)).Seconds())
		if left < 1 {
			left = 1
		}
		return "", 0, left, fmt.Errorf("发送过于频繁，请 %d 秒后再试", left)
	}
	code = s.fixedCode
	if code == "" {
		var e error
		code, e = randomDigits(6)
		if e != nil {
			return "", 0, 0, e
		}
	}
	s.byKey[k] = &Record{
		Code:      code,
		ExpiresAt: now.Add(s.ttl),
		SentAt:    now,
	}
	return code, int(s.ttl.Seconds()), 0, nil
}

// Consume 校验并消费验证码（成功后删除）。
func (s *Store) Consume(purpose Purpose, phone, code string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.gcLocked()
	k := key(purpose, phone)
	rec, ok := s.byKey[k]
	if !ok || time.Now().After(rec.ExpiresAt) {
		delete(s.byKey, k)
		return fmt.Errorf("验证码无效或已过期")
	}
	if rec.Code != code {
		rec.Attempts++
		if rec.Attempts >= s.maxAttempts {
			delete(s.byKey, k)
			return fmt.Errorf("验证码错误次数过多，请重新获取")
		}
		return fmt.Errorf("验证码错误")
	}
	delete(s.byKey, k)
	return nil
}

func (s *Store) gcLocked() {
	now := time.Now()
	for k, r := range s.byKey {
		if now.After(r.ExpiresAt.Add(time.Minute)) {
			delete(s.byKey, k)
		}
	}
}

func randomDigits(n int) (string, error) {
	b := make([]byte, n)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	out := make([]byte, n)
	for i := range b {
		out[i] = '0' + b[i]%10
	}
	return string(out), nil
}
