import { BaseService, CoolCommException } from '@cool-midway/core';
import { Config, Inject, Provide } from '@midwayjs/core';
import { InjectEntityModel } from '@midwayjs/typeorm';
import axios from 'axios';
import * as crypto from 'crypto';
import * as moment from 'moment';
import { Equal, Repository } from 'typeorm';
import { v1 as uuid } from 'uuid';
import { BaseSysParamEntity } from '../../base/entity/sys/param';
import { PluginService } from '../../plugin/service/info';
import { UserInfoEntity } from '../entity/info';
import { UserWxEntity } from '../entity/wx';

/**
 * 微信
 */
@Provide()
export class UserWxService extends BaseService {
  @Config('module.user')
  config;

  @Config('wxMini')
  wxMini: { appId?: string; appSecret?: string };

  @InjectEntityModel(UserInfoEntity)
  userInfoEntity: Repository<UserInfoEntity>;

  @InjectEntityModel(UserWxEntity)
  userWxEntity: Repository<UserWxEntity>;

  @InjectEntityModel(BaseSysParamEntity)
  baseSysParamEntity: Repository<BaseSysParamEntity>;

  @Inject()
  pluginService: PluginService;

  private miniTokenCache: { appId: string; token: string; expireAt: number };

  /**
   * 获得插件实例
   * @returns
   */
  async getPlugin() {
    try {
      const wxPlugin: any = await this.pluginService.getInstance('wx');
      return wxPlugin;
    } catch (error) {
      throw new CoolCommException(
        '未配置微信插件，请到插件市场下载安装配置：https://cool-js.com/plugin/70'
      );
    }
  }

  /**
   * 获得小程序实例
   * @returns
   */
  async getMiniApp() {
    const wxPlugin: any = await this.getPlugin();
    return wxPlugin.MiniApp();
  }

  async readParam(key: string) {
    const row = await this.baseSysParamEntity.findOneBy({ keyName: key });
    return (row?.data || '').trim();
  }

  /**
   * 小程序账号。优先插件，其次系统参数 wx.mini.appId / wx.mini.appSecret
   */
  async getMiniCredential() {
    try {
      const app = await this.getMiniApp();
      const account = app.getAccount();
      const appId = account.getAppId?.() || account.appId;
      const secret = account.getSecret?.() || account.appSecret || account.secret;
      if (appId && secret) {
        return { appId, secret };
      }
    } catch (error) {
      // 未安装微信插件时改用系统参数
    }

    const paramAppId = await this.readParam('wx.mini.appId');
    const paramSecret = await this.readParam('wx.mini.appSecret');
    const appId = paramAppId || this.wxMini?.appId || '';
    const secret = paramSecret || this.wxMini?.appSecret || '';
    if (!appId || !secret) {
      throw new CoolCommException(
        '未配置小程序密钥。请在系统参数填写 wx.mini.appSecret（微信公众平台-开发管理-开发设置）'
      );
    }
    return { appId, secret };
  }

  async getMiniAccessToken() {
    const { appId, secret } = await this.getMiniCredential();
    const now = Date.now();
    if (
      this.miniTokenCache &&
      this.miniTokenCache.appId === appId &&
      this.miniTokenCache.expireAt > now
    ) {
      return this.miniTokenCache.token;
    }

    const { data } = await axios.get('https://api.weixin.qq.com/cgi-bin/token', {
      params: {
        grant_type: 'client_credential',
        appid: appId,
        secret,
      },
    });
    if (!data?.access_token) {
      throw new CoolCommException(data?.errmsg || '获取微信凭证失败，请检查 AppSecret');
    }
    this.miniTokenCache = {
      appId,
      token: data.access_token,
      expireAt: now + Math.max(60, (data.expires_in || 7200) - 120) * 1000,
    };
    return data.access_token;
  }

  /**
   * 获得公众号实例
   * @returns
   */
  async getOfficialAccount() {
    const wxPlugin: any = await this.getPlugin();
    return wxPlugin.OfficialAccount();
  }

  /**
   * 获得App实例
   * @returns
   */
  async getOpenPlatform() {
    const wxPlugin: any = await this.getPlugin();
    return wxPlugin.OpenPlatform();
  }

  /**
   * 获得用户的openId
   * @param userId
   * @param type 0-小程序 1-公众号 2-App
   */
  async getOpenid(userId: string, type = 0) {
    const user = await this.userInfoEntity.findOneBy({
      unionid: Equal(userId),
      status: 1,
    });
    if (!user) {
      throw new CoolCommException('用户不存在或已被禁用');
    }
    const wx = await this.userWxEntity
      .createQueryBuilder('a')
      .where('a.type = :type', { type })
      .andWhere('(a.unionid = :unionid or a.openid =:openid )', {
        unionid: user.unionid,
        openid: user.unionid,
      })
      .getOne();
    return wx ? wx.openid : null;
  }

  /**
   * 获得微信配置
   * @param appId
   * @param appSecret
   * @param url 当前网页的URL，不包含#及其后面部分(必须是调用JS接口页面的完整URL)
   */
  public async getWxMpConfig(url: string) {
    const token = await this.getWxToken();
    const ticket = await axios.get(
      'https://api.weixin.qq.com/cgi-bin/ticket/getticket',
      {
        params: {
          access_token: token,
          type: 'jsapi',
        },
      }
    );

    const account = (await this.getOfficialAccount()).getAccount();
    const appid = account.getAppId();
    // 返回结果集
    const result = {
      timestamp: parseInt(moment().valueOf() / 1000 + ''),
      nonceStr: uuid(),
      appId: appid, //appid
      signature: '',
    };
    const signArr = [];
    signArr.push('jsapi_ticket=' + ticket.data.ticket);
    signArr.push('noncestr=' + result.nonceStr);
    signArr.push('timestamp=' + result.timestamp);
    signArr.push('url=' + decodeURI(url));
    // 敏感信息加密处理
    result.signature = crypto
      .createHash('sha1')
      .update(signArr.join('&'))
      .digest('hex')
      .toUpperCase();
    return result;
  }

  /**
   * 获得公众号用户信息
   * @param code
   */
  async mpUserInfo(code) {
    const token = await this.openOrMpToken(code, 'mp');
    return await this.openOrMpUserInfo(token);
  }

  /**
   * 获得app用户信息
   * @param code
   */
  async appUserInfo(code) {
    const token = await this.openOrMpToken(code, 'open');
    return await this.openOrMpUserInfo(token);
  }

  /**
   * 获得微信token 不用code
   * @param appid
   * @param secret
   */
  public async getWxToken(type = 'mp') {
    let app;
    if (type == 'mp') {
      app = await this.getOfficialAccount();
    } else {
      app = await this.getOpenPlatform();
    }
    return await app.getAccessToken().getToken();
  }

  /**
   * 获得用户信息
   * @param token
   */
  async openOrMpUserInfo(token) {
    return await axios
      .get('https://api.weixin.qq.com/sns/userinfo', {
        params: {
          access_token: token.access_token,
          openid: token.openid,
          lang: 'zh_CN',
        },
      })
      .then(res => {
        return res.data;
      });
  }

  /**
   * 获得token嗯
   * @param code
   * @param type
   */
  async openOrMpToken(code, type = 'mp') {
    const account =
      type == 'mp'
        ? (await this.getOfficialAccount()).getAccount()
        : (await this.getMiniApp()).getAccount();
    const result = await axios.get(
      'https://api.weixin.qq.com/sns/oauth2/access_token',
      {
        params: {
          appid: account.getAppId(),
          secret: account.getSecret(),
          code,
          grant_type: 'authorization_code',
        },
      }
    );
    return result.data;
  }

  /**
   * 获得小程序session
   * @param code 微信code
   * @param conf 配置
   */
  async miniSession(code) {
    const { appId, secret } = await this.getMiniCredential();
    const { data } = await axios.get(
      'https://api.weixin.qq.com/sns/jscode2session',
      {
        params: {
          appid: appId,
          secret,
          js_code: code,
          grant_type: 'authorization_code',
        },
      }
    );
    console.log('[miniSession] appId=', appId, 'code前6位=', String(code).slice(0,6), 'ret=', JSON.stringify(data));
    return data;
  }

  /**
   * 获得小程序用户信息
   * @param code
   * @param encryptedData
   * @param iv
   */
  async miniUserInfo(code, encryptedData, iv) {
    const session = await this.miniSession(code);
    if (session.errcode) {
      throw new CoolCommException('登录失败，请重试');
    }
    const info: any = await this.miniDecryptData(
      encryptedData,
      iv,
      session.session_key
    );
    if (info) {
      delete info['watermark'];
      return {
        ...info,
        openid: session['openid'],
        unionid: session['unionid'],
      };
    }
    return null;
  }

  /**
   * 获得小程序手机
   * @param code
   * @param encryptedData
   * @param iv
   */
  async miniPhone(code, encryptedData, iv) {
    // 新版获取手机号只返回动态令牌，由服务端向微信换取手机号
    if (!encryptedData || !iv) {
      return await this.getPhoneNumber(code);
    }

    const session = await this.miniSession(code);
    if (session.errcode) {
      throw new CoolCommException('获取手机号失败，请刷新重试');
    }
    const result = await this.miniDecryptData(
      encryptedData,
      iv,
      session.session_key
    );
    return result.phoneNumber;
  }

  /**
   * 用手机号动态令牌换取手机号
   * @param code getPhoneNumber 返回的 code
   */
  async getPhoneNumber(code: string) {
    if (!code) {
      throw new CoolCommException('未获取到手机号授权');
    }

    const accessToken = await this.getMiniAccessToken();

    const { data } = await axios.post(
      'https://api.weixin.qq.com/wxa/business/getuserphonenumber',
      { code },
      {
        params: {
          access_token: accessToken,
        },
      }
    );

    if (data?.errcode) {
      throw new CoolCommException(data.errmsg || '获取手机号失败');
    }

    const phone =
      data?.phone_info?.purePhoneNumber || data?.phone_info?.phoneNumber;

    if (!phone) {
      throw new CoolCommException('获得手机号失败，请检查配置');
    }

    return phone;
  }

  /**
   * 小程序信息解密
   * @param encryptedData
   * @param iv
   * @param sessionKey
   */
  async miniDecryptData(encryptedData, iv, sessionKey) {
    const app = await this.getMiniApp();
    const utils = app.getUtils();
    return await utils.decryptSession(sessionKey, iv, encryptedData);
  }
}
