/**
 * Functional tests: L4 (TCP/UDP) proxy routing.
 *
 * Creates real L4 proxy hosts pointing at echo containers,
 * then sends raw TCP connections and UDP datagrams through Caddy
 * and asserts the traffic reaches the upstream.
 *
 * Test ports exposed on the Caddy container:
 *   TCP: 15432, 15433
 *   UDP: 15353
 *
 * Upstream services:
 *   tcp-echo (cjimti/go-echo on port 9000) — echoes TCP data
 *   udp-echo (alpine/socat on port 9001) — echoes UDP datagrams
 */
import { test, expect } from '@playwright/test';
import { createL4ProxyHost } from '../../helpers/l4-proxy-api';
import { tcpSend, waitForTcpRoute, tcpConnect, udpSend, waitForUdpRoute } from '../../helpers/tcp';

const TCP_PORT = 15432;
const TCP_PORT_2 = 15433;
const UDP_PORT = 15353;

// ---------------------------------------------------------------------------
// TCP routing
// ---------------------------------------------------------------------------

test.describe.serial('L4 TCP Proxy Routing', () => {
  test('setup: create TCP proxy host pointing at tcp-echo', async ({ page }) => {
    await createL4ProxyHost(page, {
      name: 'L4 TCP Echo Test',
      protocol: 'tcp',
      listenAddress: `:${TCP_PORT}`,
      upstream: 'tcp-echo:9000',
    });
    await waitForTcpRoute('127.0.0.1', TCP_PORT);
  });

  test('routes TCP traffic to the upstream echo server', async () => {
    const res = await tcpSend('127.0.0.1', TCP_PORT, 'hello from test\n');
    expect(res.connected).toBe(true);
    expect(res.data).toContain('hello from test');
  });

  test('TCP connection is accepted on the L4 port', async () => {
    const connected = await tcpConnect('127.0.0.1', TCP_PORT);
    expect(connected).toBe(true);
  });

  test('unused TCP port does not echo data back', async () => {
    // Docker accepts the TCP connection at the container level even without a Caddy listener,
    // but no data should be proxied/echoed back since there's no L4 host configured on this port.
    const res = await tcpSend('127.0.0.1', TCP_PORT_2, 'probe', 2000);
    expect(res.data).not.toContain('probe');
  });

  test('disabled TCP proxy host stops proxying data', async ({ page }) => {
    await page.goto('/l4-proxy-hosts');
    const row = page.locator('tr', { hasText: 'L4 TCP Echo Test' });
    await row.getByRole('switch').click();
    await page.waitForTimeout(3_000);

    // Docker still accepts the TCP connection, but Caddy should not proxy/echo data
    const res = await tcpSend('127.0.0.1', TCP_PORT, 'should-not-echo\n', 2000);
    expect(res.data).not.toContain('should-not-echo');

    // Re-enable and wait for route to come back
    await row.getByRole('switch').click();
    await waitForTcpRoute('127.0.0.1', TCP_PORT);
  });
});

test.describe.serial('L4 Multiple TCP Hosts', () => {
  test('setup: create second TCP proxy host on different port', async ({ page }) => {
    await createL4ProxyHost(page, {
      name: 'L4 TCP Echo Test 2',
      protocol: 'tcp',
      listenAddress: `:${TCP_PORT_2}`,
      upstream: 'tcp-echo:9000',
    });
    await waitForTcpRoute('127.0.0.1', TCP_PORT_2);
  });

  test('both TCP ports route traffic independently', async () => {
    // Ensure both ports are ready (port 1 may have been toggled in earlier test)
    await waitForTcpRoute('127.0.0.1', TCP_PORT);
    await waitForTcpRoute('127.0.0.1', TCP_PORT_2);
    const res1 = await tcpSend('127.0.0.1', TCP_PORT, 'port1\n');
    const res2 = await tcpSend('127.0.0.1', TCP_PORT_2, 'port2\n');
    expect(res1.connected).toBe(true);
    expect(res2.connected).toBe(true);
    expect(res1.data).toContain('port1');
    expect(res2.data).toContain('port2');
  });
});

// ---------------------------------------------------------------------------
// UDP routing
// ---------------------------------------------------------------------------

test.describe.serial('L4 UDP Proxy Routing', () => {
  test('setup: create UDP proxy host pointing at udp-echo', async ({ page }) => {
    await createL4ProxyHost(page, {
      name: 'L4 UDP Echo Test',
      protocol: 'udp',
      listenAddress: `:${UDP_PORT}`,
      upstream: 'udp-echo:9001',
    });
    // UDP listeners may take longer to start than TCP — use a longer timeout
    await waitForUdpRoute('127.0.0.1', UDP_PORT, 30_000);
  });

  test('routes UDP datagrams to the upstream echo server', async () => {
    const res = await udpSend('127.0.0.1', UDP_PORT, 'hello udp');
    expect(res.received).toBe(true);
    expect(res.data).toContain('hello udp');
  });

  test('sends multiple UDP datagrams independently', async () => {
    const res1 = await udpSend('127.0.0.1', UDP_PORT, 'datagram-1');
    const res2 = await udpSend('127.0.0.1', UDP_PORT, 'datagram-2');
    expect(res1.received).toBe(true);
    expect(res2.received).toBe(true);
    expect(res1.data).toContain('datagram-1');
    expect(res2.data).toContain('datagram-2');
  });

  test('disabled UDP proxy host stops responding', async ({ page }) => {
    await page.goto('/l4-proxy-hosts');
    const row = page.locator('tr', { hasText: 'L4 UDP Echo Test' });
    await row.getByRole('switch').click();
    await page.waitForTimeout(3_000);

    const res = await udpSend('127.0.0.1', UDP_PORT, 'should-fail', 2000);
    expect(res.received).toBe(false);

    // Re-enable
    await row.getByRole('switch').click();
    await page.waitForTimeout(2_000);
  });
});
