import crypto from 'node:crypto';
import fs from 'node:fs';
import { fetch } from 'undici';

const API_BASE = 'https://api.mch.weixin.qq.com';

function readPrivateKey(config) {
  if (config.privateKeyPem) return config.privateKeyPem;
  if (config.privateKeyPath && fs.existsSync(config.privateKeyPath)) {
    return fs.readFileSync(config.privateKeyPath, 'utf8');
  }
  return null;
}

function readPlatformCert(config) {
  if (config.platformCertPem) return config.platformCertPem;
  if (config.platformCertPath && fs.existsSync(config.platformCertPath)) {
    return fs.readFileSync(config.platformCertPath, 'utf8');
  }
  return null;
}

function resolveNotifyUrl() {
  const notifyUrl = process.env.H5_WECHAT_NOTIFY_URL?.trim() ?? '';
  const publicBaseUrl = process.env.H5_PUBLIC_BASE_URL?.trim() ?? '';
  return (
    notifyUrl ||
    (publicBaseUrl ? `${publicBaseUrl.replace(/\/$/, '')}/webhooks/wechat-pay/notify` : '')
  );
}

function detectApiVersion({ explicitVersion, apiV2Key, v3Ready }) {
  const normalized = explicitVersion?.trim().toLowerCase();
  if (normalized === 'v2' || normalized === '2') return 'v2';
  if (normalized === 'v3' || normalized === '3') return 'v3';
  if (v3Ready) return 'v3';
  if (apiV2Key) return 'v2';
  return null;
}

export function loadWechatPayConfig() {
  const enabled = process.env.H5_WECHAT_PAY_ENABLED === '1';
  const appId = process.env.H5_WECHAT_APP_ID?.trim() ?? '';
  const mchId = process.env.H5_WECHAT_MCH_ID?.trim() ?? '';
  const apiV2Key =
    process.env.H5_WECHAT_API_V2_KEY?.trim() ??
    process.env.H5_WECHAT_API_KEY?.trim() ??
    '';
  const apiV3Key = process.env.H5_WECHAT_API_V3_KEY?.trim() ?? '';
  const serialNo = process.env.H5_WECHAT_SERIAL_NO?.trim() ?? '';
  const privateKeyPath = process.env.H5_WECHAT_PRIVATE_KEY_PATH?.trim() ?? '';
  const privateKeyPem = process.env.H5_WECHAT_PRIVATE_KEY?.replace(/\\n/g, '\n').trim() ?? '';
  const platformCertPath = process.env.H5_WECHAT_PLATFORM_CERT_PATH?.trim() ?? '';
  const platformCertPem =
    process.env.H5_WECHAT_PLATFORM_CERT?.replace(/\\n/g, '\n').trim() ?? '';
  const resolvedNotifyUrl = resolveNotifyUrl();
  const skipNotifyVerify = process.env.H5_WECHAT_SKIP_NOTIFY_VERIFY === '1';

  const privateKey = readPrivateKey({ privateKeyPath, privateKeyPem });
  const platformCert = readPlatformCert({ platformCertPath, platformCertPem });
  const v3Ready = Boolean(appId && mchId && apiV3Key && serialNo && privateKey && resolvedNotifyUrl);
  const apiVersion = detectApiVersion({
    explicitVersion: process.env.H5_WECHAT_API_VERSION,
    apiV2Key,
    v3Ready: Boolean(apiV3Key && serialNo && privateKey),
  });

  if (apiVersion === 'v2') {
    const configured = Boolean(appId && mchId && apiV2Key && resolvedNotifyUrl);
    return {
      enabled: enabled && configured,
      apiVersion: 'v2',
      appId,
      mchId,
      apiKey: apiV2Key,
      notifyUrl: resolvedNotifyUrl,
      skipNotifyVerify,
    };
  }

  const configured = v3Ready;
  return {
    enabled: enabled && configured,
    apiVersion: configured ? 'v3' : apiVersion,
    appId,
    mchId,
    apiV3Key,
    serialNo,
    privateKey,
    platformCert,
    notifyUrl: resolvedNotifyUrl,
    skipNotifyVerify,
  };
}

function randomNonce(length = 32) {
  return crypto.randomBytes(length).toString('hex').slice(0, length);
}

export function signV2Params(params, apiKey) {
  const stringA = Object.keys(params)
    .filter((key) => key !== 'sign' && params[key] !== undefined && params[key] !== '')
    .sort()
    .map((key) => `${key}=${params[key]}`)
    .join('&');
  const stringSignTemp = `${stringA}&key=${apiKey}`;
  return crypto.createHash('md5').update(stringSignTemp, 'utf8').digest('hex').toUpperCase();
}

export function buildXml(fields) {
  const parts = ['<xml>'];
  for (const [key, value] of Object.entries(fields)) {
    if (value === undefined || value === '') continue;
    parts.push(`<${key}><![CDATA[${value}]]></${key}>`);
  }
  parts.push('</xml>');
  return parts.join('');
}

export function parseXmlFields(xml) {
  const result = {};
  const re = /<(\w+)>(?:<!\[CDATA\[(.*?)\]\]>|([^<]*))<\/\1>/g;
  let match = re.exec(xml);
  while (match) {
    result[match[1]] = match[2] ?? match[3] ?? '';
    match = re.exec(xml);
  }
  return result;
}

function normalizeV2Transaction(fields) {
  return {
    out_trade_no: fields.out_trade_no,
    transaction_id: fields.transaction_id,
    trade_state: fields.result_code === 'SUCCESS' ? 'SUCCESS' : fields.result_code,
    result_code: fields.result_code,
    return_code: fields.return_code,
    total_fee: fields.total_fee ? Number(fields.total_fee) : undefined,
    amount: fields.total_fee ? { total: Number(fields.total_fee) } : undefined,
  };
}

async function wechatV2Request(config, path, fields) {
  const payload = {
    appid: config.appId,
    mch_id: config.mchId,
    nonce_str: randomNonce(),
    ...fields,
  };
  payload.sign = signV2Params(payload, config.apiKey);
  const body = buildXml(payload);
  const res = await fetch(`${API_BASE}${path}`, {
    method: 'POST',
    headers: { 'Content-Type': 'text/xml' },
    body,
  });
  const text = await res.text();
  const data = parseXmlFields(text);
  if (!res.ok) {
    throw new Error(`微信支付请求失败 (${res.status}): ${text}`);
  }
  if (data.return_code !== 'SUCCESS') {
    throw new Error(`微信支付请求失败: ${data.return_msg || data.return_code || text}`);
  }
  if (data.result_code !== 'SUCCESS') {
    throw new Error(`微信支付下单失败: ${data.err_code_des || data.err_code || text}`);
  }
  return data;
}

function signMessage(message, privateKeyPem) {
  return crypto.createSign('RSA-SHA256').update(message).sign(privateKeyPem, 'base64');
}

function buildAuthorization({ mchId, serialNo, privateKey }, method, pathWithQuery, body) {
  const timestamp = String(Math.floor(Date.now() / 1000));
  const nonce = randomNonce();
  const payload = body ?? '';
  const message = `${method}\n${pathWithQuery}\n${timestamp}\n${nonce}\n${payload}\n`;
  const signature = signMessage(message, privateKey);
  const authorization = [
    'WECHATPAY2-SHA256-RSA2048',
    `mchid="${mchId}"`,
    `nonce_str="${nonce}"`,
    `signature="${signature}"`,
    `timestamp="${timestamp}"`,
    `serial_no="${serialNo}"`,
  ].join(',');
  return { authorization, timestamp, nonce, signature };
}

async function wechatV3Request(config, method, path, bodyObj) {
  const body = bodyObj ? JSON.stringify(bodyObj) : '';
  const { authorization } = buildAuthorization(config, method, path, body);
  const res = await fetch(`${API_BASE}${path}`, {
    method,
    headers: {
      Authorization: authorization,
      Accept: 'application/json',
      'Content-Type': 'application/json',
    },
    body: body || undefined,
  });
  const text = await res.text();
  let data = null;
  try {
    data = text ? JSON.parse(text) : null;
  } catch {
    data = { raw: text };
  }
  if (!res.ok) {
    const detail = data?.message ?? data?.detail ?? text;
    throw new Error(`微信支付请求失败 (${res.status}): ${detail}`);
  }
  return data;
}

export function buildV2JsapiParams(config, prepayId) {
  const timeStamp = String(Math.floor(Date.now() / 1000));
  const nonceStr = randomNonce(16);
  const pkg = `prepay_id=${prepayId}`;
  const signType = 'MD5';
  const paySign = signV2Params(
    {
      appId: config.appId,
      timeStamp,
      nonceStr,
      package: pkg,
      signType,
    },
    config.apiKey,
  );
  return { appId: config.appId, timeStamp, nonceStr, package: pkg, signType, paySign };
}

export function buildV3JsapiParams(config, prepayId) {
  const timeStamp = String(Math.floor(Date.now() / 1000));
  const nonceStr = randomNonce(16);
  const pkg = `prepay_id=${prepayId}`;
  const message = `${config.appId}\n${timeStamp}\n${nonceStr}\n${pkg}\n`;
  const paySign = signMessage(message, config.privateKey);
  return { appId: config.appId, timeStamp, nonceStr, package: pkg, signType: 'RSA', paySign };
}

function createV2Client(config) {
  const createNativeOrder = async ({ outTradeNo, description, amountCents, clientIp }) => {
    const data = await wechatV2Request(config, '/pay/unifiedorder', {
      body: description,
      out_trade_no: outTradeNo,
      total_fee: String(amountCents),
      spbill_create_ip: clientIp || '127.0.0.1',
      notify_url: config.notifyUrl,
      trade_type: 'NATIVE',
    });
    if (!data.code_url) {
      throw new Error('微信支付未返回二维码链接');
    }
    return { codeUrl: data.code_url };
  };

  const createH5Order = async ({ outTradeNo, description, amountCents, clientIp }) => {
    const appUrl = process.env.H5_PUBLIC_BASE_URL ?? 'https://go.tkmind.cn';
    const data = await wechatV2Request(config, '/pay/unifiedorder', {
      body: description,
      out_trade_no: outTradeNo,
      total_fee: String(amountCents),
      spbill_create_ip: clientIp || '127.0.0.1',
      notify_url: config.notifyUrl,
      trade_type: 'MWEB',
      scene_info: JSON.stringify({
        h5_info: {
          type: 'Wap',
          wap_url: appUrl,
          wap_name: 'TKMind',
        },
      }),
    });
    if (!data.mweb_url) {
      throw new Error('微信支付未返回 H5 支付链接');
    }
    return { h5Url: data.mweb_url };
  };

  const createJsapiOrder = async ({ outTradeNo, description, amountCents, clientIp, openid }) => {
    if (!openid) {
      throw new Error('JSAPI 支付缺少 openid');
    }
    const data = await wechatV2Request(config, '/pay/unifiedorder', {
      body: description,
      out_trade_no: outTradeNo,
      total_fee: String(amountCents),
      spbill_create_ip: clientIp || '127.0.0.1',
      notify_url: config.notifyUrl,
      trade_type: 'JSAPI',
      openid,
    });
    if (!data.prepay_id) {
      throw new Error('微信支付未返回 prepay_id');
    }
    return { jsapiParams: buildV2JsapiParams(config, data.prepay_id) };
  };

  const verifyNotify = ({ headers: _headers, body }) => {
    const bodyText = typeof body === 'string' ? body : String(body ?? '');
    const fields = parseXmlFields(bodyText);
    if (!fields.out_trade_no) {
      throw new Error('微信支付回调缺少商户订单号');
    }
    if (fields.return_code !== 'SUCCESS') {
      throw new Error(`微信支付回调失败: ${fields.return_msg || fields.return_code}`);
    }
    if (!config.skipNotifyVerify) {
      const sign = fields.sign;
      if (!sign) {
        throw new Error('微信支付回调缺少签名');
      }
      const expected = signV2Params(fields, config.apiKey);
      if (sign !== expected) {
        throw new Error('微信支付回调验签失败');
      }
    }
    return { transaction: normalizeV2Transaction(fields) };
  };

  return {
    enabled: true,
    apiVersion: 'v2',
    appId: config.appId,
    createNativeOrder,
    createH5Order,
    createJsapiOrder,
    verifyNotify,
  };
}

function createV3Client(config) {
  const createNativeOrder = async ({ outTradeNo, description, amountCents, clientIp }) => {
    const data = await wechatV3Request(config, 'POST', '/v3/pay/transactions/native', {
      appid: config.appId,
      mchid: config.mchId,
      description,
      out_trade_no: outTradeNo,
      notify_url: config.notifyUrl,
      amount: { total: amountCents, currency: 'CNY' },
      scene_info: clientIp ? { payer_client_ip: clientIp } : undefined,
    });
    return { codeUrl: data.code_url };
  };

  const createH5Order = async ({ outTradeNo, description, amountCents, clientIp }) => {
    const data = await wechatV3Request(config, 'POST', '/v3/pay/transactions/h5', {
      appid: config.appId,
      mchid: config.mchId,
      description,
      out_trade_no: outTradeNo,
      notify_url: config.notifyUrl,
      amount: { total: amountCents, currency: 'CNY' },
      scene_info: {
        payer_client_ip: clientIp || '127.0.0.1',
        h5_info: {
          type: 'Wap',
          app_name: 'TKMind',
          app_url: process.env.H5_PUBLIC_BASE_URL ?? 'https://go.tkmind.cn',
        },
      },
    });
    return { h5Url: data.h5_url };
  };

  const createJsapiOrder = async ({ outTradeNo, description, amountCents, clientIp, openid }) => {
    if (!openid) {
      throw new Error('JSAPI 支付缺少 openid');
    }
    const data = await wechatV3Request(config, 'POST', '/v3/pay/transactions/jsapi', {
      appid: config.appId,
      mchid: config.mchId,
      description,
      out_trade_no: outTradeNo,
      notify_url: config.notifyUrl,
      amount: { total: amountCents, currency: 'CNY' },
      payer: { openid },
      scene_info: clientIp ? { payer_client_ip: clientIp } : undefined,
    });
    if (!data.prepay_id) {
      throw new Error('微信支付未返回 prepay_id');
    }
    return { jsapiParams: buildV3JsapiParams(config, data.prepay_id) };
  };

  const verifyNotify = ({ headers, body }) => {
    const timestamp = headers['wechatpay-timestamp'] ?? headers['Wechatpay-Timestamp'];
    const nonce = headers['wechatpay-nonce'] ?? headers['Wechatpay-Nonce'];
    const signature = headers['wechatpay-signature'] ?? headers['Wechatpay-Signature'];
    const serial = headers['wechatpay-serial'] ?? headers['Wechatpay-Serial'];

    if (!timestamp || !nonce || !signature) {
      throw new Error('微信支付回调缺少签名头');
    }

    const bodyText = typeof body === 'string' ? body : JSON.stringify(body);
    const message = `${timestamp}\n${nonce}\n${bodyText}\n`;

    if (!config.skipNotifyVerify) {
      if (!config.platformCert) {
        throw new Error('未配置微信支付平台证书，无法验签');
      }
      const verified = crypto
        .createVerify('RSA-SHA256')
        .update(message)
        .verify(config.platformCert, signature, 'base64');
      if (!verified) {
        throw new Error('微信支付回调验签失败');
      }
    }

    const payload = typeof body === 'string' ? JSON.parse(body) : body;
    const resource = payload?.resource;
    if (!resource?.ciphertext || !resource?.nonce) {
      throw new Error('微信支付回调数据不完整');
    }

    const cipherBuf = Buffer.from(resource.ciphertext, 'base64');
    const authTag = cipherBuf.subarray(cipherBuf.length - 16);
    const dataBuf = cipherBuf.subarray(0, cipherBuf.length - 16);
    const decipher = crypto.createDecipheriv(
      'aes-256-gcm',
      Buffer.from(config.apiV3Key, 'utf8'),
      Buffer.from(resource.nonce, 'utf8'),
    );
    decipher.setAuthTag(authTag);
    if (resource.associated_data) {
      decipher.setAAD(Buffer.from(resource.associated_data, 'utf8'));
    }
    const plain = Buffer.concat([decipher.update(dataBuf), decipher.final()]).toString('utf8');
    const transaction = JSON.parse(plain);
    return { serial, transaction };
  };

  return {
    enabled: true,
    apiVersion: 'v3',
    appId: config.appId,
    createNativeOrder,
    createH5Order,
    createJsapiOrder,
    verifyNotify,
  };
}

export function createWechatPayClient(config) {
  if (!config?.enabled) {
    return {
      enabled: false,
      apiVersion: config?.apiVersion ?? null,
      appId: config?.appId ?? null,
      async createNativeOrder() {
        throw new Error('微信支付未配置');
      },
      async createH5Order() {
        throw new Error('微信支付未配置');
      },
      async createJsapiOrder() {
        throw new Error('微信支付未配置');
      },
      verifyNotify() {
        throw new Error('微信支付未配置');
      },
    };
  }

  if (config.apiVersion === 'v2') {
    return createV2Client(config);
  }
  return createV3Client(config);
}

export const WECHAT_NOTIFY_SUCCESS_V2 =
  '<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>';
