// ============================================================
// GoogleProvider — Google Sign-In ID Token 验证
//
// ── Flutter google_sign_in 流程 ─────────────────────────────
//   1. 客户端调用 GoogleSignIn().signIn()
//   2. 用户选择 Google 账号，完成授权
//   3. 获取 GoogleSignInAuthentication.idToken（JWT 字符串）
//   4. 将 idToken 发送到 POST /api/v1/auth/google
//
// ── 服务端验证方式 ──────────────────────────────────────────
//   GET https://oauth2.googleapis.com/tokeninfo?id_token=TOKEN
//   Google 服务器验证 Token 签名 + 有效期，返回用户信息
//   响应字段: sub (user ID), email, name, picture
//
//   注意: 此方法简单可靠，但每次登录都会请求 Google API。
//   生产优化: 缓存 Google JWKS 公钥 (https://www.googleapis.com/oauth2/v3/certs)，
//   本地验证 JWT 签名（减少 RTT，提升性能）。
//
// ── 申请 Google OAuth Client ID 步骤 ──────────────────────
// 1. 登录 Google Cloud Console
//    https://console.cloud.google.com → 选择/创建项目
//
// 2. 启用 API
//    API 和服务 → 库 → 搜索「Google Sign-In API」→ 启用
//
// 3. 创建凭据
//    API 和服务 → 凭据 → 创建凭据 → OAuth 2.0 客户端 ID
//    应用类型:
//      Android: 包名 cn.gogenex.consumer + SHA-1 指纹（debug/release 各一个）
//      iOS:     Bundle ID cn.gogenex.consumer
//      Web:     （可选，用于服务端验证 Client ID）
//
// 4. 获取 Client ID（形如 xxxxxxxx.apps.googleusercontent.com）
//    Android/iOS 客户端 ID 填入 Flutter 配置
//    服务端用同一 Client ID 验证 idToken 的 aud 字段
//
// ── 环境变量配置 ───────────────────────────────────────────
//   GOOGLE_CLIENT_ID — OAuth 2.0 Client ID，用于验证 idToken 的 audience
//
// ── 安全注意事项 ───────────────────────────────────────────
//   - 务必验证 aud 字段 = GOOGLE_CLIENT_ID，防止其他应用的 token 被接受
//   - token 有效期通常 1 小时，tokeninfo 接口会自动验证 exp 字段
// ============================================================

import { Injectable, Logger, BadRequestException, UnauthorizedException } from '@nestjs/common';
import * as https from 'https';

/** Google tokeninfo 接口响应 */
export interface GoogleTokenInfo {
  sub: string;           // 用户唯一 ID（全局不变）
  email?: string;        // 邮箱（若授权了 email scope）
  email_verified?: string;
  name?: string;         // 显示名
  picture?: string;      // 头像 URL
  given_name?: string;
  family_name?: string;
  aud: string;           // Client ID，必须与 GOOGLE_CLIENT_ID 匹配
  exp: string;           // 过期时间（Unix 秒）
  error?: string;        // 若 token 无效时返回
  error_description?: string;
}

@Injectable()
export class GoogleProvider {
  private readonly logger = new Logger('GoogleProvider');
  private readonly clientId = process.env.GOOGLE_CLIENT_ID;

  /**
   * 通过 Google tokeninfo 接口验证 ID Token 并返回用户信息
   *
   * 实现方式选择：
   *   当前: 调用 Google tokeninfo 接口（简单，Google 服务端验证签名 + 有效期）
   *   替代: 本地 JWKS 验证（获取 https://www.googleapis.com/oauth2/v3/certs 缓存公钥，
   *          本地验证 JWT 签名，减少一次网络 RTT，适合高并发场景）
   *   当前选择理由: tokeninfo 接口更简单，且每次登录频率不高，外部 RTT 可接受
   *
   * tokeninfo 接口响应（token 有效时返回用户信息，无效时返回 error）：
   *   sub:            用户全局唯一 ID（不随账号信息变化，作为 openid 存储）
   *   email:          用户邮箱（Google 账号通常已验证）
   *   email_verified: 邮箱是否已验证（'true'/'false'）
   *   name:           显示名（given_name + family_name）
   *   picture:        头像 URL（可能包含 =s96-c 等尺寸参数）
   *   aud:            Client ID（必须与配置的 GOOGLE_CLIENT_ID 一致）
   *   exp:            过期时间（tokeninfo 接口已验证，不需要重复检查）
   *
   * 错误响应（token 无效时）:
   *   { "error": "invalid_token", "error_description": "Token has been expired..." }
   *
   * @param idToken Flutter google_sign_in 返回的 JWT 格式 ID Token（通常1小时有效）
   */
  async verifyIdToken(idToken: string): Promise<GoogleTokenInfo> {
    // tokeninfo 会自动验证 JWT 签名 + exp 有效期，无需手动处理
    const data = await this.httpsGet(
      `https://oauth2.googleapis.com/tokeninfo?id_token=${encodeURIComponent(idToken)}`,
    );

    const info = JSON.parse(data) as GoogleTokenInfo;

    // 若 token 无效，Google 返回 { error: '...', error_description: '...' }
    if (info.error) {
      this.logger.error(`Google token invalid: ${info.error} - ${info.error_description}`);
      throw new UnauthorizedException(`Google 登录失败: ${info.error_description || info.error}`);
    }

    // 验证 audience：防止接受其他应用颁发的 token（安全必要）
    // GOOGLE_CLIENT_ID 未配置时跳过此验证（开发便利，生产必须配置）
    if (this.clientId && info.aud !== this.clientId) {
      this.logger.error(`Google token audience mismatch: ${info.aud} != ${this.clientId}`);
      throw new UnauthorizedException('Google Token 不属于此应用');
    }

    this.logger.log(`Google token verified: sub=${info.sub.slice(0, 6)}... email=${info.email}`);
    return info;
  }

  private httpsGet(url: string): Promise<string> {
    return new Promise((resolve, reject) => {
      const req = https.get(url, (res) => {
        let body = '';
        res.on('data', (chunk) => (body += chunk));
        res.on('end', () => resolve(body));
      });
      req.on('error', reject);
      req.setTimeout(10000, () => {
        req.destroy();
        reject(new Error('Google API timeout'));
      });
    });
  }
}
