import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import Dysmsapi20170525, * as $Dysmsapi20170525 from '@alicloud/dysmsapi20170525';
import * as $OpenApi from '@alicloud/openapi-client';
import * as $Util from '@alicloud/tea-util';

export interface SmsSendResult {
  success: boolean;
  requestId?: string;
  bizId?: string;
  code?: string;
  message?: string;
  uncertain?: boolean; // 超时情况下短信可能已发送
}

// 重试配置
const RETRY_CONFIG = {
  maxRetries: 4, // 最大重试次数
  baseDelay: 6000, // 基础延迟 6 秒
  maxDelay: 30000, // 最大延迟 30 秒
  // 只对连接阶段的错误重试，读取超时不重试（可能短信已发送）
  retryableErrors: [
    'ECONNRESET',
    'ECONNREFUSED',
    'ENOTFOUND', // DNS 解析失败
    'ServiceUnavailable',
  ],
  // 超时错误（短信可能已发送成功，返回成功但标记为不确定）
  timeoutErrors: [
    'ConnectTimeout',
    'ReadTimeout',
    'ETIMEDOUT',
  ],
  // 不重试的业务错误
  noRetryErrors: [
    'BUSINESS_LIMIT_CONTROL', // 流控
  ],
};

@Injectable()
export class SmsService implements OnModuleInit {
  private readonly logger = new Logger(SmsService.name);
  private client: Dysmsapi20170525 | null = null;
  private readonly signName: string;
  private readonly templateCode: string;
  private readonly enabled: boolean;

  constructor(private readonly configService: ConfigService) {
    const smsConfig = this.configService.get('smsConfig') || {};
    const aliyunConfig = smsConfig.aliyun || {};

    this.signName =
      aliyunConfig.signName ||
      this.configService.get('ALIYUN_SMS_SIGN_NAME', '榴莲皇后');
    this.templateCode =
      aliyunConfig.templateCode ||
      this.configService.get('ALIYUN_SMS_TEMPLATE_CODE', '');
    this.enabled =
      smsConfig.enabled ?? this.configService.get('SMS_ENABLED') === 'true';
  }

  async onModuleInit() {
    await this.initClient();
  }

  private async initClient(): Promise<void> {
    const accessKeyId = this.configService.get<string>('ALIYUN_ACCESS_KEY_ID');
    const accessKeySecret = this.configService.get<string>(
      'ALIYUN_ACCESS_KEY_SECRET',
    );
    const endpoint = this.configService.get<string>(
      'ALIYUN_SMS_ENDPOINT',
      'dysmsapi.aliyuncs.com',
    );

    if (!accessKeyId || !accessKeySecret) {
      this.logger.warn('阿里云 SMS 配置缺失，短信功能将使用模拟模式');
      return;
    }

    try {
      const config = new $OpenApi.Config({
        accessKeyId,
        accessKeySecret,
        endpoint,
      });

      this.client = new Dysmsapi20170525(config);
      this.logger.log('阿里云 SMS 客户端初始化成功');
    } catch (error) {
      this.logger.error('阿里云 SMS 客户端初始化失败', error);
    }
  }

  /**
   * 发送验证码短信（带重试机制）
   *
   * @param phoneNumber 手机号码（支持国际格式，如 +86xxx）
   * @param code 验证码
   * @returns 发送结果
   */
  async sendVerificationCode(
    phoneNumber: string,
    code: string,
  ): Promise<SmsSendResult> {
    // 标准化手机号（去除 +86 前缀）
    const normalizedPhone = this.normalizePhoneNumber(phoneNumber);

    this.logger.log(
      `[SMS] 发送验证码到 ${this.maskPhoneNumber(normalizedPhone)}`,
    );

    // 开发环境或未启用时，使用模拟模式
    if (!this.enabled || !this.client) {
      this.logger.warn(
        `[SMS] 模拟模式: 验证码 ${code} 发送到 ${this.maskPhoneNumber(normalizedPhone)}`,
      );
      return {
        success: true,
        requestId: 'mock-request-id',
        bizId: 'mock-biz-id',
        code: 'OK',
        message: '模拟发送成功',
      };
    }

    // 带重试的发送
    return this.sendWithRetry(normalizedPhone, code);
  }

  /**
   * 带重试机制的短信发送
   */
  private async sendWithRetry(
    phoneNumber: string,
    code: string,
  ): Promise<SmsSendResult> {
    let lastError: any = null;

    for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
      try {
        if (attempt > 0) {
          // 指数退避延迟
          const delay = Math.min(
            RETRY_CONFIG.baseDelay * Math.pow(2, attempt - 1),
            RETRY_CONFIG.maxDelay,
          );
          this.logger.log(
            `[SMS] 第 ${attempt} 次重试，延迟 ${delay}ms...`,
          );
          await this.sleep(delay);
        }

        const result = await this.doSendSms(phoneNumber, code);

        if (result.success) {
          if (attempt > 0) {
            this.logger.log(
              `[SMS] 重试成功 (第 ${attempt} 次): requestId=${result.requestId}`,
            );
          }
          return result;
        }

        // 业务错误（如频率限制）不重试
        if (!this.isRetryableError(result.code || '')) {
          return result;
        }

        lastError = new Error(result.message || 'SMS send failed');
        (lastError as any).code = result.code;
      } catch (error: any) {
        lastError = error;
        const errorInfo = error.message || error.code || '';

        // 超时错误：短信可能已发送成功，返回成功但标记为不确定
        if (this.isTimeoutError(errorInfo)) {
          this.logger.warn(
            `[SMS] 超时但可能已发送成功: ${error.message}`,
          );
          return {
            success: true,
            uncertain: true,
            code: 'TIMEOUT_UNCERTAIN',
            message: '请求超时，验证码可能已发送，请稍等片刻',
          };
        }

        // 检查是否是可重试的错误
        if (!this.isRetryableError(errorInfo)) {
          this.logger.error(`[SMS] 不可重试错误: ${error.message}`);
          return {
            success: false,
            code: error.code || 'UNKNOWN_ERROR',
            message: error.message || '短信发送失败',
          };
        }

        this.logger.warn(
          `[SMS] 发送失败 (尝试 ${attempt + 1}/${RETRY_CONFIG.maxRetries + 1}): ${error.message}`,
        );
      }
    }

    // 所有重试都失败
    this.logger.error(
      `[SMS] 发送失败，已重试 ${RETRY_CONFIG.maxRetries} 次: ${lastError?.message}`,
    );
    return {
      success: false,
      code: lastError?.code || 'RETRY_EXHAUSTED',
      message: `短信发送失败，已重试 ${RETRY_CONFIG.maxRetries} 次: ${lastError?.message || '未知错误'}`,
    };
  }

  /**
   * 实际发送短信
   */
  private async doSendSms(
    phoneNumber: string,
    code: string,
  ): Promise<SmsSendResult> {
    const sendSmsRequest = new $Dysmsapi20170525.SendSmsRequest({
      phoneNumbers: phoneNumber,
      signName: this.signName,
      templateCode: this.templateCode,
      templateParam: JSON.stringify({ code }),
    });

    const runtime = new $Util.RuntimeOptions({
      connectTimeout: 15000, // 连接超时 15 秒
      readTimeout: 15000, // 读取超时 15 秒
    });

    const response = await this.client!.sendSmsWithOptions(
      sendSmsRequest,
      runtime,
    );

    const body = response.body;
    const result: SmsSendResult = {
      success: body?.code === 'OK',
      requestId: body?.requestId,
      bizId: body?.bizId,
      code: body?.code,
      message: body?.message,
    };

    if (result.success) {
      this.logger.log(
        `[SMS] 发送成功: requestId=${result.requestId}, bizId=${result.bizId}`,
      );
    } else {
      this.logger.error(
        `[SMS] 发送失败: code=${result.code}, message=${result.message}`,
      );
    }

    return result;
  }

  /**
   * 检查是否是可重试的错误
   */
  private isRetryableError(errorInfo: string): boolean {
    return RETRY_CONFIG.retryableErrors.some(
      (retryable) => errorInfo.includes(retryable),
    );
  }

  /**
   * 检查是否是超时错误
   */
  private isTimeoutError(errorInfo: string): boolean {
    return RETRY_CONFIG.timeoutErrors.some(
      (timeout) => errorInfo.includes(timeout),
    );
  }

  /**
   * 睡眠函数
   */
  private sleep(ms: number): Promise<void> {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  /**
   * 发送通用短信
   *
   * @param phoneNumber 手机号码
   * @param templateCode 模板代码
   * @param templateParam 模板参数
   * @returns 发送结果
   */
  async sendSms(
    phoneNumber: string,
    templateCode: string,
    templateParam: Record<string, string>,
  ): Promise<SmsSendResult> {
    const normalizedPhone = this.normalizePhoneNumber(phoneNumber);

    if (!this.enabled || !this.client) {
      this.logger.warn(
        `[SMS] 模拟模式: 模板 ${templateCode} 发送到 ${this.maskPhoneNumber(normalizedPhone)}`,
      );
      return {
        success: true,
        requestId: 'mock-request-id',
        code: 'OK',
        message: '模拟发送成功',
      };
    }

    try {
      const sendSmsRequest = new $Dysmsapi20170525.SendSmsRequest({
        phoneNumbers: normalizedPhone,
        signName: this.signName,
        templateCode,
        templateParam: JSON.stringify(templateParam),
      });

      const runtime = new $Util.RuntimeOptions({
        connectTimeout: 10000, // 连接超时 10 秒
        readTimeout: 10000, // 读取超时 10 秒
      });
      const response = await this.client.sendSmsWithOptions(
        sendSmsRequest,
        runtime,
      );

      const body = response.body;
      return {
        success: body?.code === 'OK',
        requestId: body?.requestId,
        bizId: body?.bizId,
        code: body?.code,
        message: body?.message,
      };
    } catch (error: any) {
      this.logger.error(`[SMS] 发送异常: ${error.message}`);
      return {
        success: false,
        code: error.code || 'UNKNOWN_ERROR',
        message: error.message || '短信发送失败',
      };
    }
  }

  /**
   * 查询短信发送状态
   *
   * @param phoneNumber 手机号码
   * @param bizId 发送回执ID
   * @param sendDate 发送日期 (yyyyMMdd 格式)
   */
  async querySendDetails(
    phoneNumber: string,
    bizId: string,
    sendDate: string,
  ): Promise<any> {
    if (!this.client) {
      this.logger.warn('[SMS] 客户端未初始化，无法查询');
      return null;
    }

    try {
      const querySendDetailsRequest =
        new $Dysmsapi20170525.QuerySendDetailsRequest({
          phoneNumber: this.normalizePhoneNumber(phoneNumber),
          bizId,
          sendDate,
          pageSize: 10,
          currentPage: 1,
        });

      const runtime = new $Util.RuntimeOptions({
        connectTimeout: 10000, // 连接超时 10 秒
        readTimeout: 10000, // 读取超时 10 秒
      });
      const response = await this.client.querySendDetailsWithOptions(
        querySendDetailsRequest,
        runtime,
      );

      return response.body;
    } catch (error: any) {
      this.logger.error(`[SMS] 查询发送详情失败: ${error.message}`);
      return null;
    }
  }

  /**
   * 标准化手机号（去除国际区号前缀）
   */
  private normalizePhoneNumber(phoneNumber: string): string {
    let normalized = phoneNumber.trim();

    // 去除 +86 或 86 前缀
    if (normalized.startsWith('+86')) {
      normalized = normalized.substring(3);
    } else if (normalized.startsWith('86') && normalized.length === 13) {
      normalized = normalized.substring(2);
    }

    return normalized;
  }

  /**
   * 脱敏手机号（用于日志）
   */
  private maskPhoneNumber(phoneNumber: string): string {
    if (phoneNumber.length < 7) {
      return phoneNumber;
    }
    return (
      phoneNumber.substring(0, 3) +
      '****' +
      phoneNumber.substring(phoneNumber.length - 4)
    );
  }
}
