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

const OAUTH_AUTHORIZE_URL = 'https://open.weixin.qq.com/connect/oauth2/authorize';
const OAUTH_QRCONNECT_URL = 'https://open.weixin.qq.com/connect/qrconnect';
const OAUTH_ACCESS_TOKEN_URL = 'https://api.weixin.qq.com/sns/oauth2/access_token';
const OAUTH_USERINFO_URL = 'https://api.weixin.qq.com/sns/userinfo';

const STATE_TTL_MS = 10 * 60 * 1000;
const VALID_INTENTS = new Set(['login', 'register', 'bind']);
const VALID_AUTH_MODES = new Set(['mp', 'open', 'scan']);

function isPollScanAuthMode(authMode) {
  return authMode === 'open' || authMode === 'scan';
}

function resolveCallbackUrl(env = process.env) {
  const explicit = env.H5_WECHAT_OAUTH_CALLBACK_URL?.trim() ?? '';
  if (explicit) return explicit;
  const base = env.H5_PUBLIC_BASE_URL?.trim() ?? '';
  if (base) return `${base.replace(/\/$/, '')}/auth/wechat/callback`;
  return '';
}

export function isWechatUserAgent(userAgent = '') {
  const ua = userAgent || '';
  return /MicroMessenger/i.test(ua) || /WindowsWechat/i.test(ua);
}

export function loadWechatOAuthConfig(env = process.env) {
  const enabledFlag = env.H5_WECHAT_OAUTH_ENABLED === '1';
  const appId = env.H5_WECHAT_APP_ID?.trim() ?? env.H5_WECHAT_OAUTH_APP_ID?.trim() ?? '';
  const appSecret = env.H5_WECHAT_APP_SECRET?.trim() ?? env.H5_WECHAT_OAUTH_APP_SECRET?.trim() ?? '';
  const openAppId = env.H5_WECHAT_OPEN_APP_ID?.trim() ?? '';
  const openAppSecret = env.H5_WECHAT_OPEN_APP_SECRET?.trim() ?? '';
  const scope = env.H5_WECHAT_OAUTH_SCOPE?.trim() || 'snsapi_userinfo';
  const callbackUrl = resolveCallbackUrl(env);
  const configured = Boolean(appId && appSecret && callbackUrl);
  const scanConfigured = Boolean(openAppId && openAppSecret && callbackUrl);
  return {
    enabled: enabledFlag && configured,
    appId,
    appSecret,
    openAppId,
    openAppSecret,
    scanEnabled: enabledFlag && scanConfigured,
    scope,
    callbackUrl,
  };
}

export function sanitizeWechatReturnTo(returnTo, req) {
  if (!returnTo || typeof returnTo !== 'string') return '/';
  const host = req.get('host');
  try {
    const url = new URL(returnTo, `http://${host}`);
    if (url.host !== host) return '/';
    if (!url.pathname.startsWith('/')) return '/';
    return `${url.pathname}${url.search}${url.hash}`;
  } catch {
    if (returnTo.startsWith('/') && !returnTo.startsWith('//')) return returnTo;
    return '/';
  }
}

function normalizeIntent(intent) {
  const value = typeof intent === 'string' ? intent.trim().toLowerCase() : 'login';
  return VALID_INTENTS.has(value) ? value : 'login';
}

function normalizeAuthMode(authMode) {
  const value = typeof authMode === 'string' ? authMode.trim().toLowerCase() : 'mp';
  return VALID_AUTH_MODES.has(value) ? value : 'mp';
}

function buildAuthorizeUrl(config, { state, authMode = 'mp' }) {
  const isOpen = authMode === 'open';
  const useMp = authMode === 'mp' || authMode === 'scan';
  const params = new URLSearchParams({
    appid: isOpen ? config.openAppId : config.appId,
    redirect_uri: config.callbackUrl,
    response_type: 'code',
    scope: isOpen ? 'snsapi_login' : config.scope,
    state,
  });
  const base = isOpen ? OAUTH_QRCONNECT_URL : OAUTH_AUTHORIZE_URL;
  return `${base}?${params.toString()}#wechat_redirect`;
}

async function exchangeCode(config, code, authMode = 'mp') {
  const isOpen = authMode === 'open';
  const params = new URLSearchParams({
    appid: isOpen ? config.openAppId : config.appId,
    secret: isOpen ? config.openAppSecret : config.appSecret,
    code,
    grant_type: 'authorization_code',
  });
  const res = await fetch(`${OAUTH_ACCESS_TOKEN_URL}?${params.toString()}`);
  const data = await res.json();
  if (data.errcode) {
    throw new Error(data.errmsg || `微信授权失败 (${data.errcode})`);
  }
  if (!data.openid) {
    throw new Error('微信授权未返回 openid');
  }
  return {
    accessToken: data.access_token,
    openid: data.openid,
    unionid: data.unionid ?? null,
    refreshToken: data.refresh_token ?? null,
    appId: isOpen ? config.openAppId : config.appId,
  };
}

async function fetchUserInfo(accessToken, openid) {
  const params = new URLSearchParams({
    access_token: accessToken,
    openid,
    lang: 'zh_CN',
  });
  const res = await fetch(`${OAUTH_USERINFO_URL}?${params.toString()}`);
  const data = await res.json();
  if (data.errcode) {
    throw new Error(data.errmsg || `获取微信用户信息失败 (${data.errcode})`);
  }
  return {
    nickname: typeof data.nickname === 'string' ? data.nickname.slice(0, 128) : null,
    avatarUrl: typeof data.headimgurl === 'string' ? data.headimgurl.slice(0, 512) : null,
    unionid: data.unionid ?? null,
  };
}

export function createWechatOAuthService(pool, config, { userAuth } = {}) {
  if (!config?.enabled) {
    return {
      enabled: false,
      publicConfig() {
        return { enabled: false, inWechat: false, scanEnabled: false };
      },
    };
  }

  const pruneExpiredStates = async (now = Date.now()) => {
    if (!pool) return;
    await pool.query(`DELETE FROM h5_wechat_oauth_states WHERE expires_at <= ?`, [now]);
  };

  const createState = async ({
    returnTo = '/',
    utmSource = null,
    utmMedium = null,
    utmCampaign = null,
    intent = 'login',
    bindUserId = null,
    authMode = 'mp',
    now = Date.now(),
  } = {}) => {
    const state = crypto.randomBytes(24).toString('base64url');
    if (pool) {
      await pruneExpiredStates(now);
      await pool.query(
        `INSERT INTO h5_wechat_oauth_states
         (state, return_to, utm_source, utm_medium, utm_campaign, intent, bind_user_id,
          auth_mode, status, expires_at, created_at)
         VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?, ?)`,
        [
          state,
          returnTo,
          utmSource,
          utmMedium,
          utmCampaign,
          normalizeIntent(intent),
          bindUserId,
          normalizeAuthMode(authMode),
          now + STATE_TTL_MS,
          now,
        ],
      );
    }
    return state;
  };

  const loadState = async (state, now = Date.now()) => {
    if (!state || !pool) return null;
    const [rows] = await pool.query(
      `SELECT state, return_to, utm_source, utm_medium, utm_campaign, intent, bind_user_id,
              auth_mode, status, result_kind, result_token, result_message, expires_at
       FROM h5_wechat_oauth_states
       WHERE state = ?
       LIMIT 1`,
      [state],
    );
    const row = rows[0];
    if (!row || Number(row.expires_at) <= now) return null;
    return row;
  };

  const consumeState = async (state, now = Date.now()) => {
    const row = await loadState(state, now);
    if (!row) return null;
    await pool.query(`DELETE FROM h5_wechat_oauth_states WHERE state = ?`, [state]);
    return {
      returnTo: row.return_to || '/',
      utmSource: row.utm_source,
      utmMedium: row.utm_medium,
      utmCampaign: row.utm_campaign,
      intent: row.intent || 'login',
      bindUserId: row.bind_user_id,
      authMode: row.auth_mode || 'mp',
    };
  };

  const markStateComplete = async (
    state,
    { resultKind, resultToken = null, resultMessage = null },
    now = Date.now(),
  ) => {
    await pool.query(
      `UPDATE h5_wechat_oauth_states
       SET status = 'done', result_kind = ?, result_token = ?, result_message = ?, expires_at = ?
       WHERE state = ?`,
      [resultKind, resultToken, resultMessage, now + STATE_TTL_MS, state],
    );
  };

  const buildAuthorizeRedirect = async (req, { bindUserId = null } = {}) => {
    const returnTo = sanitizeWechatReturnTo(req.query?.return_to, req);
    const intent = normalizeIntent(req.query?.intent);
    const authMode = normalizeAuthMode(req.query?.auth_mode);
    if (authMode === 'open' && !config.scanEnabled) {
      throw new Error('微信扫码登录未配置');
    }
    const state = await createState({
      returnTo,
      utmSource: typeof req.query?.utm_source === 'string' ? req.query.utm_source : null,
      utmMedium: typeof req.query?.utm_medium === 'string' ? req.query.utm_medium : null,
      utmCampaign: typeof req.query?.utm_campaign === 'string' ? req.query.utm_campaign : null,
      intent,
      bindUserId,
      authMode,
    });
    return buildAuthorizeUrl(config, { state, authMode });
  };

  const startScanLogin = async (req) => {
    const returnTo = sanitizeWechatReturnTo(req.query?.return_to, req);
    const utmSource = typeof req.query?.utm_source === 'string' ? req.query.utm_source : null;
    const utmMedium = typeof req.query?.utm_medium === 'string' ? req.query.utm_medium : null;
    const utmCampaign = typeof req.query?.utm_campaign === 'string' ? req.query.utm_campaign : null;
    const common = {
      returnTo,
      utmSource,
      utmMedium,
      utmCampaign,
      intent: 'login',
    };

    if (config.scanEnabled) {
      const state = await createState({ ...common, authMode: 'open' });
      return {
        mode: 'open',
        state,
        openAppId: config.openAppId,
        redirectUri: config.callbackUrl,
        expiresInMs: STATE_TTL_MS,
      };
    }

    const state = await createState({ ...common, authMode: 'scan' });
    return {
      mode: 'mp',
      state,
      qrUrl: buildAuthorizeUrl(config, { state, authMode: 'scan' }),
      expiresInMs: STATE_TTL_MS,
    };
  };

  const pollScanLogin = async (state, now = Date.now()) => {
    const row = await loadState(state, now);
    if (!row) return { status: 'expired' };
    if (row.status !== 'done') return { status: 'pending' };
    await pool.query(`DELETE FROM h5_wechat_oauth_states WHERE state = ?`, [state]);
    if (row.result_kind === 'error') {
      return { status: 'error', message: row.result_message || '微信登录失败' };
    }
    if (row.result_kind === 'binding_gate') {
      return { status: 'binding_gate', pendingToken: row.result_token };
    }
    if (row.result_kind === 'login') {
      return { status: 'complete', token: row.result_token };
    }
    return { status: 'error', message: '微信登录结果无效' };
  };

  const handleCallback = async ({ code, state, ip = 'unknown' }) => {
    if (!code) {
      throw new Error('缺少微信授权 code');
    }
    const stateRow = await loadState(state);
    if (!stateRow) {
      throw new Error('授权状态无效或已过期，请重新登录');
    }

    const authMode = stateRow.auth_mode || 'mp';
    const tokenInfo = await exchangeCode(config, code, authMode);
    let nickname = null;
    let avatarUrl = null;
    let unionid = tokenInfo.unionid;

    if ((authMode === 'mp' || authMode === 'scan') && config.scope === 'snsapi_userinfo') {
      const profile = await fetchUserInfo(tokenInfo.accessToken, tokenInfo.openid);
      nickname = profile.nickname;
      avatarUrl = profile.avatarUrl;
      unionid = profile.unionid ?? unionid;
    }
    if (authMode === 'open') {
      try {
        const profile = await fetchUserInfo(tokenInfo.accessToken, tokenInfo.openid);
        nickname = profile.nickname;
        avatarUrl = profile.avatarUrl;
        unionid = profile.unionid ?? unionid;
      } catch {
        // snsapi_login may not always return userinfo; proceed with openid only
      }
    }

    if (!userAuth?.resolveWechatAuth) {
      throw new Error('用户系统未就绪');
    }

    const result = await userAuth.resolveWechatAuth({
      appId: tokenInfo.appId,
      openid: tokenInfo.openid,
      unionid,
      nickname,
      avatarUrl,
      intent: stateRow.intent || 'login',
      bindUserId: stateRow.bind_user_id,
      returnTo: stateRow.return_to || '/',
      utmSource: stateRow.utm_source,
      utmMedium: stateRow.utm_medium,
      utmCampaign: stateRow.utm_campaign,
      ip,
    });
    if (!result.ok) {
      if (isPollScanAuthMode(authMode)) {
        await markStateComplete(state, {
          resultKind: 'error',
          resultMessage: result.message || '微信登录失败',
        });
        return {
          authMode,
          action: 'poll_error',
          message: result.message || '微信登录失败',
        };
      }
      throw new Error(result.message || '微信登录失败');
    }

    if (result.action === 'binding_gate') {
      if (isPollScanAuthMode(authMode)) {
        await markStateComplete(state, {
          resultKind: 'binding_gate',
          resultToken: result.pendingToken,
        });
        return {
          authMode,
          action: 'binding_gate',
          pendingToken: result.pendingToken,
          wechatProfile: result.wechatProfile,
          returnTo: result.returnTo,
        };
      }
      return {
        authMode,
        action: 'binding_gate',
        pendingToken: result.pendingToken,
        wechatProfile: result.wechatProfile,
        returnTo: result.returnTo,
        utmSource: result.utmSource,
        utmMedium: result.utmMedium,
        utmCampaign: result.utmCampaign,
      };
    }

    if (isPollScanAuthMode(authMode)) {
      await markStateComplete(state, {
        resultKind: 'login',
        resultToken: result.token,
      });
      return {
        authMode,
        action: 'login',
        token: result.token,
        user: result.user,
        isNewUser: result.isNewUser,
        returnTo: stateRow.return_to || '/',
        utmSource: stateRow.utm_source,
        utmMedium: stateRow.utm_medium,
        utmCampaign: stateRow.utm_campaign,
      };
    }

    await pool.query(`DELETE FROM h5_wechat_oauth_states WHERE state = ?`, [state]);
    return {
      authMode,
      action: 'login',
      token: result.token,
      user: result.user,
      isNewUser: result.isNewUser,
      bound: result.bound,
      returnTo: stateRow.return_to || '/',
      utmSource: stateRow.utm_source,
      utmMedium: stateRow.utm_medium,
      utmCampaign: stateRow.utm_campaign,
    };
  };

  return {
    enabled: true,
    publicConfig(req) {
      const inWechat = req ? isWechatUserAgent(req.get?.('user-agent') || '') : false;
      return {
        enabled: true,
        inWechat,
        scanEnabled: true,
        openScanEnabled: Boolean(config.scanEnabled),
        appId: config.appId,
        openAppId: config.scanEnabled ? config.openAppId : undefined,
        oauthCallbackUrl: config.scanEnabled ? config.callbackUrl : undefined,
      };
    },
    buildAuthorizeRedirect,
    startScanLogin,
    pollScanLogin,
    handleCallback,
  };
}
