import { Inject, Injectable, InternalServerErrorException, Logger } from '@nestjs/common'
import weixinConfig from './weixin.config'
import { ConfigType } from '@nestjs/config'
import {
    ApiConfig,
    ApiConfigKit,
    AccessTokenApi,
    SnsAccessTokenApi,
    ScopeEnum,
    PayKit,
    WX_DOMAIN,
    WX_API_TYPE,
    Kits,
    JsTicketApi,
    JsApiType,
    AccessToken,
    JsTicket
} from 'tnwx'
import { InjectRepository } from '@nestjs/typeorm'
import { AccessTokenCache } from './entities/access-token-cache.entity'
import { LessThan, MoreThan, Not, Repository } from 'typeorm'
import { addSeconds, differenceInMinutes } from 'date-fns'
import { readFileSync, writeFileSync, statSync } from 'fs'
import { JsapiTicketCache } from './entities/jsapi-ticket-cache.entity'
import { Attach } from './dto/attach.dto'
import { join } from 'path'
import BigNumber from 'bignumber.js'

@Injectable()
export class WeixinService {
    private privateKey: Buffer
    private publicKey: Buffer
    private platformPlublicKey: Buffer
    constructor(
        @Inject(weixinConfig.KEY)
        private readonly weixinConfiguration: ConfigType<typeof weixinConfig>,
        @InjectRepository(AccessTokenCache)
        private readonly accessTokenRepository: Repository<AccessTokenCache>,
        @InjectRepository(JsapiTicketCache)
        private readonly jsapiTicketRepository: Repository<JsapiTicketCache>
    ) {
        let apiConfig = new ApiConfig(weixinConfiguration.appId, weixinConfiguration.appSecret, '')
        ApiConfigKit.putApiConfig(apiConfig)
        ApiConfigKit.devMode = true
        ApiConfigKit.setCurrentAppId(apiConfig.getAppId)
        this.privateKey = readFileSync(join(__dirname, '..', '..', 'certs', 'apiclient_key.pem'))
        this.publicKey = readFileSync(join(__dirname, '..', '..', 'certs', 'apiclient_cert.pem'))
        this.platformPlublicKey = readFileSync(join(__dirname, '..', '..', 'certs', 'platform_cert.pem'))
        this.getCert()
    }

    async getAccessToken(): Promise<AccessToken> {
        return await AccessTokenApi.getAccessToken()
    }

    async getJsapiTicket() {
        return await JsTicketApi.getTicket(JsApiType.JSAPI, await this.getAccessToken())
    }

    async refreshAccessToken(): Promise<AccessToken> {
        const res: AccessToken = await AccessTokenApi.getAccessToken()
        Logger.log(JSON.stringify(res))
        Logger.log(res.getJson)
        if (res.getErrCode != 0) {
            throw new InternalServerErrorException(res.getErrMsg)
        }
        const newToken = await this.accessTokenRepository.save(
            new AccessTokenCache(res.getJson, addSeconds(new Date(), res.getExpiresIn - 300))
        )
        await this.accessTokenRepository.delete({ id: Not(newToken.id) })
        return res
    }

    async refreshTicket() {
        const res: JsTicket = await JsTicketApi.getTicket(JsApiType.JSAPI, await this.getAccessToken())
        Logger.log(JSON.stringify(res))
        Logger.log(res.getJson)
        if (res.getErrCode != 0) {
            throw new InternalServerErrorException(res.getErrMsg)
        }
        const newTicket = await this.jsapiTicketRepository.save(
            new JsapiTicketCache(res.getJson, addSeconds(new Date(), res.getExpiresIn - 300))
        )
        await this.jsapiTicketRepository.delete({ id: Not(newTicket.id) })
        return res
    }

    getRedirectUrl(url: string, state?: string) {
        return SnsAccessTokenApi.getAuthorizeUrl(url, ScopeEnum.SNSAPI_BASE, state)
    }

    async code2oenId(code: string) {
        const res = await SnsAccessTokenApi.getSnsAccessToken(code)
        if (res.errcode) {
            throw new InternalServerErrorException(res.errmsg)
        }
        return res.openid
    }

    async jsapiPay(description: string, amount: number, openid: string, attach: Attach) {
        let realAmount = new BigNumber(amount)
        if (realAmount.comparedTo(new BigNumber(100)) >= 0) {
            realAmount = realAmount.minus(new BigNumber((Math.random() * 2).toFixed(2)))
        }
        let data = {
            appid: this.weixinConfiguration.appId,
            mchid: this.weixinConfiguration.mchId,
            description: description,
            out_trade_no: Kits.generateStr(),
            notify_url: this.weixinConfiguration.notifyUrl,
            amount: {
                total: parseInt(realAmount.times(new BigNumber(100)).toFixed(0)),
                currency: 'CNY'
            },
            payer: {
                openid
            },
            attach: JSON.stringify(attach)
        }
        let result = await PayKit.exePost(
            WX_DOMAIN.CHINA, //
            WX_API_TYPE.JS_API_PAY,
            this.weixinConfiguration.mchId,
            this.weixinConfiguration.certSerial,
            this.privateKey,
            JSON.stringify(data)
        )
        Logger.log(JSON.stringify(result.data, null, 2), 'jsapi下单')
        if (result.status === 200) {
            const timeStamp = parseInt((new Date().getTime() / 1000).toString()).toString()
            const nonceStr = Kits.generateStr()
            const packageStr = `prepay_id=${result.data.prepay_id}`
            const signType = 'RSA'
            const signStr =
                [this.weixinConfiguration.appId, timeStamp, nonceStr, packageStr].join(String.fromCharCode(10)) +
                String.fromCharCode(10)
            const paySign = Kits.sha256WithRsa(signStr, this.privateKey)
            return {
                appId: this.weixinConfiguration.appId,
                timestamp: timeStamp,
                nonceStr,
                package: packageStr,
                signType,
                paySign
            }
        } else {
            throw new InternalServerErrorException(result.data.message)
        }
    }

    async getCert() {
        try {
            const certPath = join(__dirname, '..', '..', 'certs', 'platform_cert.pem')

            let result = await PayKit.exeGet(
                WX_DOMAIN.CHINA, //
                WX_API_TYPE.GET_CERTIFICATES,
                this.weixinConfiguration.mchId,
                this.weixinConfiguration.certSerial,
                this.privateKey
            )
            Logger.log('OK', '获取平台证书')
            const data = result.data as WechatPay.GetCertResponse
            let headers = result.headers
            let serial = headers['wechatpay-serial']
            let timestamp = headers['wechatpay-timestamp']
            let nonce = headers['wechatpay-nonce']
            let signature = headers['wechatpay-signature']

            // 根据序列号查证书  验证签名
            // let verifySignature: boolean = PayKit.verifySignature(signature, data, nonce, timestamp, wxPublicKey)
            let verifySignature: boolean = PayKit.verifySign(headers, JSON.stringify(data), this.platformPlublicKey)
            Logger.log(verifySignature, '验证签名')

            result.data.data.sort((a, b) => {
                return a.effective_time > b.effective_time ? -1 : 1
            })
            let decrypt = PayKit.aes256gcmDecrypt(
                this.weixinConfiguration.mchKey,
                data.data[0].encrypt_certificate.nonce,
                data.data[0].encrypt_certificate.associated_data,
                data.data[0].encrypt_certificate.ciphertext
            )
            // 保存证书
            writeFileSync(certPath, decrypt)
            this.platformPlublicKey = readFileSync(certPath)
            Logger.log(certPath, '保存平台证书')
            return data
        } catch (error) {
            Logger.error(error)
        }
    }

    async jsapiSign(url: string) {
        const noncestr = Kits.generateStr()
        const timestamp = parseInt((new Date().getTime() / 1000).toString())
        const jsapiTicket = (await this.getJsapiTicket()).getTicket
        const str = `jsapi_ticket=${jsapiTicket}&noncestr=${noncestr}&timestamp=${timestamp}&url=${url}`
        const signature = Kits.sha1(str)
        Logger.log(`\n${url}\n${jsapiTicket}\n${noncestr}\n${timestamp}\n${url}`, 'jsapiSign body')
        Logger.log(signature, 'jsapiSign signature')
        return {
            appId: this.weixinConfiguration.appId,
            timestamp,
            nonceStr: noncestr,
            signature
        }
    }

    async getNotifyData(headers, data: WechatPay.NotifyEvent) {
        Logger.log(JSON.stringify(headers, null, 2), '微信回调Header')
        Logger.log(JSON.stringify(data, null, 2), '微信回调Body')
        let verifySignature: boolean = PayKit.verifySign(headers, JSON.stringify(data), this.platformPlublicKey)
        Logger.log(verifySignature, '验证签名')
        if (!verifySignature) {
            throw new InternalServerErrorException('签名验证失败')
        }
        let notifyData: WechatPay.NotifyData = JSON.parse(
            PayKit.aes256gcmDecrypt(
                this.weixinConfiguration.mchKey,
                data.resource.nonce,
                data.resource.associated_data,
                data.resource.ciphertext
            )
        )
        Logger.log(JSON.stringify(notifyData, null, 2), '解密数据')
        return notifyData
    }
}
