import crypto from 'node:crypto';
import fs from 'node:fs';
import { settings } from './config.js';

const PROXY_TYPES = new Set(['vless', 'vmess', 'trojan', 'shadowsocks', 'hysteria2']);

export function getHwid() {
  fs.mkdirSync(settings.dataDir, { recursive: true });
  if (fs.existsSync(settings.hwidPath)) {
    return fs.readFileSync(settings.hwidPath, 'utf8').trim();
  }
  const hwid = crypto.randomBytes(8).toString('hex');
  fs.writeFileSync(settings.hwidPath, hwid, 'utf8');
  return hwid;
}

export function subscriptionHeaders() {
  return {
    'user-agent': 'singbox',
    'x-hwid': getHwid(),
    'x-device-os': process.platform,
    'x-ver-os': process.version,
    'x-device-model': settings.appName,
  };
}

export function parseUserInfo(headerValue) {
  const result = {};
  if (!headerValue) return result;

  for (const part of String(headerValue).split(';')) {
    const [key, value] = part.trim().split('=', 2);
    if (!key || value === undefined) continue;
    const parsed = Number.parseInt(value, 10);
    if (!Number.isNaN(parsed)) result[key] = parsed;
  }

  return result;
}

export function parseVlessUrl(rawUrl) {
  if (!rawUrl.startsWith('vless://')) {
    throw new Error('VLESS URL must start with vless://');
  }

  const parsed = new URL(rawUrl);
  const tag = decodeURIComponent(parsed.hash ? parsed.hash.slice(1) : 'vless-out');
  const uuid = decodeURIComponent(parsed.username || '');
  const server = parsed.hostname;
  const serverPort = Number.parseInt(parsed.port || '443', 10);
  const publicKey = parsed.searchParams.get('pbk') || '';
  const shortId = parsed.searchParams.get('sid') || '';
  const serverName = parsed.searchParams.get('sni') || server;
  const fingerprint = parsed.searchParams.get('fp') || 'chrome';
  const flow = parsed.searchParams.get('flow') || '';

  if (!uuid || !server || !serverPort) {
    throw new Error('VLESS URL misses uuid, host or port');
  }

  if (!publicKey || !shortId) {
    throw new Error('VLESS REALITY parameters pbk and sid are required');
  }

  return {
    type: 'vless',
    tag,
    server,
    server_port: serverPort,
    uuid,
    flow,
    tls: {
      enabled: true,
      server_name: serverName,
      utls: {
        enabled: true,
        fingerprint,
      },
      reality: {
        enabled: true,
        public_key: publicKey,
        short_id: shortId,
      },
    },
    packet_encoding: 'xudp',
  };
}

function maybeDecodeBase64(content) {
  const compact = content.trim().replace(/\s+/g, '');
  if (!compact || !/^[A-Za-z0-9+/=]+$/.test(compact)) return content;

  try {
    const decoded = Buffer.from(compact, 'base64').toString('utf8');
    if (decoded.includes('vless://') || decoded.includes('{')) return decoded;
  } catch {}

  return content;
}

export function parseSubscriptionBody(body) {
  let parsedConfig = null;

  try {
    parsedConfig = JSON.parse(body);
  } catch {
    const decoded = maybeDecodeBase64(body);
    const links = decoded
      .split(/\r?\n/)
      .map((line) => line.trim())
      .filter((line) => line.startsWith('vless://'));

    if (!links.length) {
      throw new Error('Subscription does not contain JSON config or VLESS links');
    }

    parsedConfig = {
      outbounds: links.map(parseVlessUrl),
    };
  }

  const outbounds = Array.isArray(parsedConfig.outbounds) ? parsedConfig.outbounds : [];
  const servers = outbounds
    .filter((outbound) => PROXY_TYPES.has(outbound.type))
    .map((outbound) => ({
      tag: outbound.tag || `${outbound.type}-${outbound.server || 'server'}`,
      type: outbound.type,
      server: outbound.server || 'unknown',
      server_port: outbound.server_port || 443,
    }));

  if (!servers.length) {
    throw new Error('No supported proxy outbounds found in subscription');
  }

  return { config: parsedConfig, servers };
}

export async function fetchSubscription(url) {
  let parsedUrl;
  try {
    parsedUrl = new URL(url);
  } catch {
    throw new Error('Invalid subscription URL');
  }

  if (!['http:', 'https:'].includes(parsedUrl.protocol)) {
    throw new Error('Subscription URL must use http or https');
  }

  const response = await fetch(parsedUrl, {
    headers: subscriptionHeaders(),
    redirect: 'follow',
  });

  if (!response.ok) {
    throw new Error(`Subscription request failed: HTTP ${response.status}`);
  }

  const body = await response.text();
  const userInfo = parseUserInfo(response.headers.get('subscription-userinfo'));
  const parsed = parseSubscriptionBody(body);

  return {
    ...parsed,
    userInfo,
    fetchedAt: new Date().toISOString(),
  };
}
