// Package verification 提供验证码管理的业务逻辑服务
// 包含短信验证码生成、发送、验证等功能
package verification

import (
	"context"
	"crypto/rand"
	"fmt"
	"math/big"

	"github.com/break/junhong_cmp_fiber/pkg/config"
	"github.com/break/junhong_cmp_fiber/pkg/constants"
	"github.com/break/junhong_cmp_fiber/pkg/errors"
	"github.com/break/junhong_cmp_fiber/pkg/sms"
	"github.com/redis/go-redis/v9"
	"go.uber.org/zap"
)

// Service 验证码服务
type Service struct {
	redisClient *redis.Client
	smsClient   *sms.Client
	logger      *zap.Logger
}

// NewService 创建验证码服务实例
func NewService(redisClient *redis.Client, smsClient *sms.Client, logger *zap.Logger) *Service {
	return &Service{
		redisClient: redisClient,
		smsClient:   smsClient,
		logger:      logger,
	}
}

// SendCode 发送验证码
func (s *Service) SendCode(ctx context.Context, phone string) error {
	// 检查短信服务是否可用
	if s.smsClient == nil {
		s.logger.Error("短信服务未配置", zap.String("phone", phone))
		return errors.New(errors.CodeServiceUnavailable)
	}

	// 检查发送频率限制
	limitKey := constants.RedisVerificationCodeLimitKey(phone)
	exists, err := s.redisClient.Exists(ctx, limitKey).Result()
	if err != nil {
		s.logger.Error("检查验证码发送频率限制失败",
			zap.String("phone", phone),
			zap.Error(err),
		)
		return errors.Wrap(errors.CodeInternalError, err, "检查验证码发送频率限制失败")
	}

	if exists > 0 {
		s.logger.Warn("验证码发送过于频繁",
			zap.String("phone", phone),
		)
		return errors.New(errors.CodeTooManyRequests, "验证码发送过于频繁，请稍后再试")
	}

	// 生成随机验证码
	code, err := s.generateCode()
	if err != nil {
		s.logger.Error("生成验证码失败",
			zap.String("phone", phone),
			zap.Error(err),
		)
		return errors.Wrap(errors.CodeInternalError, err, "生成验证码失败")
	}

	// 构造短信内容
	cfg := config.Get()
	content := fmt.Sprintf("您的验证码是%s，%d分钟内有效", code, int(constants.VerificationCodeExpiration.Minutes()))

	// 发送短信
	_, err = s.smsClient.SendMessage(ctx, content, []string{phone})
	if err != nil {
		s.logger.Error("发送验证码短信失败",
			zap.String("phone", phone),
			zap.Error(err),
		)
		return errors.Wrap(errors.CodeInternalError, err, "发送验证码短信失败")
	}

	// 存储验证码到 Redis
	codeKey := constants.RedisVerificationCodeKey(phone)
	err = s.redisClient.Set(ctx, codeKey, code, constants.VerificationCodeExpiration).Err()
	if err != nil {
		s.logger.Error("存储验证码失败",
			zap.String("phone", phone),
			zap.Error(err),
		)
		return errors.Wrap(errors.CodeInternalError, err, "存储验证码失败")
	}

	// 设置发送频率限制
	err = s.redisClient.Set(ctx, limitKey, "1", constants.VerificationCodeRateLimit).Err()
	if err != nil {
		s.logger.Error("设置验证码发送频率限制失败",
			zap.String("phone", phone),
			zap.Error(err),
		)
		// 这个错误不影响主流程，只记录日志
	}

	s.logger.Info("验证码发送成功",
		zap.String("phone", phone),
	)

	// 避免在日志中暴露验证码（仅在开发环境下记录）
	if cfg.Logging.Development {
		s.logger.Debug("验证码内容（仅开发环境）",
			zap.String("phone", phone),
			zap.String("code", code),
		)
	}

	return nil
}

// VerifyCode 验证验证码
func (s *Service) VerifyCode(ctx context.Context, phone string, code string) error {
	codeKey := constants.RedisVerificationCodeKey(phone)

	// 从 Redis 获取验证码
	storedCode, err := s.redisClient.Get(ctx, codeKey).Result()
	if err == redis.Nil {
		s.logger.Warn("验证码不存在或已过期",
			zap.String("phone", phone),
		)
		return errors.New(errors.CodeInvalidParam, "验证码不存在或已过期")
	}
	if err != nil {
		s.logger.Error("获取验证码失败",
			zap.String("phone", phone),
			zap.Error(err),
		)
		return errors.Wrap(errors.CodeInternalError, err, "获取验证码失败")
	}

	// 验证码比对
	if storedCode != code {
		s.logger.Warn("验证码错误",
			zap.String("phone", phone),
		)
		return errors.New(errors.CodeInvalidParam, "验证码错误")
	}

	// 验证成功，删除验证码（防止重复使用）
	err = s.redisClient.Del(ctx, codeKey).Err()
	if err != nil {
		s.logger.Error("删除验证码失败",
			zap.String("phone", phone),
			zap.Error(err),
		)
		// 这个错误不影响主流程，只记录日志
	}

	s.logger.Info("验证码验证成功",
		zap.String("phone", phone),
	)

	return nil
}

// generateCode 生成随机验证码
func (s *Service) generateCode() (string, error) {
	// 生成 6 位数字验证码
	const digits = "0123456789"
	code := make([]byte, constants.VerificationCodeLength)

	for i := range code {
		num, err := rand.Int(rand.Reader, big.NewInt(int64(len(digits))))
		if err != nil {
			return "", err
		}
		code[i] = digits[num.Int64()]
	}

	return string(code), nil
}
