import {
  BadRequestException,
  Injectable,
  InternalServerErrorException,
  UnauthorizedException,
} from '@nestjs/common';
import { MESSAGES } from '../../common/messages.js';

type WechatAccessTokenResponse = {
  access_token?: string;
  expires_in?: number;
  errcode?: number;
  errmsg?: string;
};

type WechatCode2SessionResponse = {
  openid?: string;
  errcode?: number;
  errmsg?: string;
};

type WechatPhoneResponse = {
  phone_info?: {
    phoneNumber?: string;
  };
  errcode?: number;
  errmsg?: string;
};

/**
 * 微信小程序认证服务：负责和微信开放接口交换 openId 与手机号。
 */
@Injectable()
export class WechatMiniAppService {
  private accessTokenCache:
    | {
        token: string;
        expiresAt: number;
      }
    | null = null;

  /**
   * 一次性解析微信登录 code 与手机号 code。
   */
  async resolvePhoneIdentity(loginCode: string, phoneCode: string) {
    const [openId, phone] = await Promise.all([
      this.exchangeLoginCode(loginCode),
      this.exchangePhoneCode(phoneCode),
    ]);

    return {
      openId,
      phone,
    };
  }

  /**
   * 通过 wx.login 返回的 code 换取 openId。
   */
  async exchangeLoginCode(loginCode: string): Promise<string> {
    const config = this.requireConfig();
    const params = new URLSearchParams({
      appid: config.appId,
      secret: config.secret,
      js_code: this.normalizeCode(loginCode, 'loginCode'),
      grant_type: 'authorization_code',
    });
    const response = await fetch(
      `https://api.weixin.qq.com/sns/jscode2session?${params.toString()}`,
    );
    const payload =
      (await response.json().catch(() => null)) as WechatCode2SessionResponse | null;

    if (!response.ok || !payload?.openid || payload.errcode) {
      throw new UnauthorizedException(
        this.buildWechatAuthErrorMessage(
          MESSAGES.AUTH.WECHAT_MINIAPP_LOGIN_FAILED,
          payload,
        ),
      );
    }

    return payload.openid;
  }

  /**
   * 通过手机号授权 code 换取手机号。
   */
  async exchangePhoneCode(phoneCode: string): Promise<string> {
    const accessToken = await this.getAccessToken();
    const response = await fetch(
      `https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=${encodeURIComponent(accessToken)}`,
      {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          code: this.normalizeCode(phoneCode, 'phoneCode'),
        }),
      },
    );
    const payload =
      (await response.json().catch(() => null)) as WechatPhoneResponse | null;
    const phone = payload?.phone_info?.phoneNumber;

    if (!response.ok || !phone || payload?.errcode) {
      throw new UnauthorizedException(
        this.buildWechatAuthErrorMessage(
          MESSAGES.AUTH.WECHAT_MINIAPP_PHONE_FAILED,
          payload,
        ),
      );
    }

    return phone;
  }

  /**
   * 获取微信小程序 access token，带简单进程内缓存。
   */
  private async getAccessToken(): Promise<string> {
    const cached = this.accessTokenCache;
    if (cached && cached.expiresAt > Date.now()) {
      return cached.token;
    }

    const config = this.requireConfig();
    const params = new URLSearchParams({
      grant_type: 'client_credential',
      appid: config.appId,
      secret: config.secret,
    });
    const response = await fetch(
      `https://api.weixin.qq.com/cgi-bin/token?${params.toString()}`,
    );
    const payload =
      (await response.json().catch(() => null)) as WechatAccessTokenResponse | null;

    if (!response.ok || !payload?.access_token || payload.errcode) {
      throw new UnauthorizedException(
        this.buildWechatAuthErrorMessage(
          MESSAGES.AUTH.WECHAT_MINIAPP_PHONE_FAILED,
          payload,
        ),
      );
    }

    this.accessTokenCache = {
      token: payload.access_token,
      expiresAt: Date.now() + Math.max((payload.expires_in ?? 7200) - 120, 60) * 1000,
    };

    return payload.access_token;
  }

  /**
   * 校验微信配置存在。
   */
  private requireConfig() {
    const appId = process.env.WECHAT_MINIAPP_APPID?.trim();
    const secret = process.env.WECHAT_MINIAPP_SECRET?.trim();

    if (!appId || !secret) {
      throw new InternalServerErrorException(
        MESSAGES.AUTH.WECHAT_MINIAPP_CONFIG_MISSING,
      );
    }

    return {
      appId,
      secret,
    };
  }

  /**
   * 登录 code 基础校验。
   */
  private normalizeCode(code: unknown, fieldName: string) {
    if (typeof code !== 'string') {
      throw new BadRequestException(`${fieldName} 必须是字符串`);
    }

    const trimmed = code.trim();
    if (!trimmed) {
      throw new BadRequestException(`${fieldName} 不能为空`);
    }

    return trimmed;
  }

  /**
   * 拼接微信开放平台原始错误，便于定位 appid/code/授权态问题。
   */
  private buildWechatAuthErrorMessage(
    fallbackMessage: string,
    payload:
      | WechatAccessTokenResponse
      | WechatCode2SessionResponse
      | WechatPhoneResponse
      | null,
  ) {
    const errcode =
      payload && typeof payload.errcode === 'number' ? payload.errcode : null;
    const errmsg =
      payload && typeof payload.errmsg === 'string' ? payload.errmsg.trim() : '';

    if (errcode == null && !errmsg) {
      return fallbackMessage;
    }

    const details = [`errcode=${errcode ?? 'unknown'}`];
    if (errmsg) {
      details.push(`errmsg=${errmsg}`);
    }

    return `${fallbackMessage}（${details.join(', ')}）`;
  }
}
