import { formatInboundLabel } from '@/lib/inbounds/label';
import { preferPublicHost, resolveShareHost } from '@/lib/xray/inbound-link';
import { effectiveMtu } from '@/lib/xray/amneziawg-obfuscation';
import type { ClientRecord, InboundOption } from '@/hooks/useClients';

// AmneziaWG clients are wire-identical to WireGuard clients (same
// privateKey/publicKey/allowedIPs/preSharedKey/keepAlive fields on
// model.Client — see wireguardConfig.ts's isWireguardClient), so this duck
// type can't tell the two protocols apart on its own; findAmneziaWGInbounds's
// protocol==='amneziawg' filter below is what actually disambiguates.
export function isAmneziaWGClient(client: ClientRecord | null | undefined): boolean {
  if (!client) return false;
  return !!(
    client.privateKey ||
    client.publicKey ||
    client.allowedIPs ||
    client.preSharedKey ||
    client.keepAlive
  );
}

export function findAmneziaWGInbounds(
  client: ClientRecord | null | undefined,
  inboundsById: Record<number, InboundOption>,
): InboundOption[] {
  return (client?.inboundIds || [])
    .map((id) => inboundsById?.[id])
    .filter((ib): ib is InboundOption => ib?.protocol === 'amneziawg');
}

// h4Line renders one H magic-header line, matching the Go backend's
// hOrDefault fallback (blank -> the classic 1/2/3/4 WireGuard message type).
function hLine(key: string, value: string | undefined, fallback: string): string {
  return `${key} = ${value && value.trim() !== '' ? value : fallback}`;
}

// addressOverride carries this inbound's own AllowedIPs (ClientHydrateSchema's
// tunnelAllowedIPs). ClientRecord.allowedIPs is a single shared column, so for
// an identity attached to both WireGuard and AmneziaWG it holds the WireGuard
// address — writing that into the AmneziaWG .conf yields an unroutable peer.
export function buildAmneziaWGClientConfig(
  client: ClientRecord,
  inbound: InboundOption | undefined,
  host = window.location.hostname,
  publicHost = '',
  addressOverride = '',
): string {
  const server = inbound?.awgServer;
  const endpointHost = resolveShareHost(
    inbound ?? {},
    inbound?.nodeAddress ?? '',
    preferPublicHost(host, publicHost),
  );
  const address = addressOverride || client.allowedIPs || '10.8.1.2/32';
  const endpoint = `${endpointHost}:${inbound?.port || ''}`;
  const inboundName = inbound ? formatInboundLabel(inbound.tag, inbound.remark) : '';
  const remark = [inboundName, client.email, client.comment].filter(Boolean).join(' - ');

  // These land unescaped in [Interface]; a newline here would inject a
  // config line (e.g. a rogue PostUp) into the downloaded .conf.
  const privateKey = client.privateKey || client.password || '';
  for (const v of [privateKey, server?.primaryDns ?? '', server?.secondaryDns ?? '', remark]) {
    if (/[\r\n]/.test(v)) return '';
  }

  const dnsParts = [server?.primaryDns, server?.secondaryDns].filter((v) => !!v && v.trim() !== '');
  const lines = ['[Interface]', `PrivateKey = ${privateKey}`, `Address = ${address}`];
  if (dnsParts.length > 0) lines.push(`DNS = ${dnsParts.join(', ')}`);
  lines.push(`MTU = ${effectiveMtu(server?.mtu, server?.s4)}`);

  // AmneziaWG obfuscation parameters — must match the server's values.
  lines.push(`Jc = ${server?.jc ?? 5}`);
  lines.push(`Jmin = ${server?.jmin ?? 10}`);
  lines.push(`Jmax = ${server?.jmax ?? 50}`);
  lines.push(`S1 = ${server?.s1 ?? 30}`);
  lines.push(`S2 = ${server?.s2 ?? 45}`);
  if (server?.s3) lines.push(`S3 = ${server.s3}`);
  if (server?.s4) lines.push(`S4 = ${server.s4}`);
  lines.push(hLine('H1', server?.h1, '1'));
  lines.push(hLine('H2', server?.h2, '2'));
  lines.push(hLine('H3', server?.h3, '3'));
  lines.push(hLine('H4', server?.h4, '4'));
  if (server?.i1) lines.push(`I1 = ${server.i1}`);
  if (server?.i2) lines.push(`I2 = ${server.i2}`);
  if (server?.i3) lines.push(`I3 = ${server.i3}`);
  if (server?.i4) lines.push(`I4 = ${server.i4}`);
  if (server?.i5) lines.push(`I5 = ${server.i5}`);
  const optional31: Array<[string, string | undefined]> = [
    ['HeaderProtectionKey', server?.headerProtectionKey],
    ['ContentPaddingAddition', server?.contentPaddingAddition],
    ['RekeyAfterTime', server?.rekeyAfterTime],
    ['RekeyTimeout', server?.rekeyTimeout],
    ['RejectAfterTime', server?.rejectAfterTime],
    ['KeepaliveTimeout', server?.keepaliveTimeout],
    ['MaxHandshakeAttempts', server?.maxHandshakeAttempts],
  ];
  for (const [key, value] of optional31) {
    if (value && value.trim() !== '') lines.push(`${key} = ${value}`);
  }
  if (server?.randomTrailers) lines.push('RandomTrailers = on');
  if (server?.disableCookies) lines.push('DisableCookies = on');

  lines.push('');
  if (remark) lines.push(`# ${remark}`);
  lines.push('[Peer]', `PublicKey = ${server?.publicKey || ''}`);
  if (client.preSharedKey) lines.push(`PresharedKey = ${client.preSharedKey}`);
  lines.push('AllowedIPs = 0.0.0.0/0, ::/0', `Endpoint = ${endpoint}`);
  if (client.keepAlive && client.keepAlive > 0)
    lines.push(`PersistentKeepalive = ${client.keepAlive}`);
  return lines.join('\n');
}
