/**
 * Regression tests for PUBLIC_ROUTES / CSRF excludedPaths `:param` placeholder
 * matching. Pre-DC-053 these were literal-string comparisons, so
 * `/api/v1/share/:token/preview` never matched real request paths like
 * `/api/v1/share/abc123/preview`. Fixed by converting `:param` to a
 * `[^/]+` regex segment before testing. Caught during DC-053 live testing.
 */

'use strict';

const fs = require('fs');
const path = require('path');
const os = require('os');

function _tmpDir() {
  return fs.mkdtempSync(path.join(os.tmpdir(), 'dashcaddy-public-test-'));
}
function _cleanup(dir) {
  try { fs.rmSync(dir, { recursive: true, force: true }); } catch {}
}

const SRC_MIDDLEWARE = path.join(__dirname, '..', 'src', 'utilities', 'middleware.js');
const SRC_CSRF = path.join(__dirname, '..', 'src', 'security', 'csrf-protection.js');

describe('PUBLIC_ROUTES + CSRF excludedPaths: `:param` placeholder matching', () => {
  test('PUBLIC_ROUTES is parsed and contains the DC-053 share entries', () => {
    const content = fs.readFileSync(SRC_MIDDLEWARE, 'utf8');
    // Sanity: file should still contain the public share entries
    expect(content).toContain('/api/v1/share/:token/preview');
    expect(content).toContain('/api/v1/share/:token/subscribe');
    expect(content).toContain('/api/v1/share/:token/redeem-tailscale');
  });

  test('CSRF excludedPaths contains the DC-053 share entries', () => {
    const content = fs.readFileSync(SRC_CSRF, 'utf8');
    expect(content).toContain('/api/v1/share/:token/subscribe');
    expect(content).toContain('/api/v1/share/:token/redeem-tailscale');
  });

  test('PUBLIC_ROUTES contains the DC-048 invite entries (regression coverage)', () => {
    const content = fs.readFileSync(SRC_MIDDLEWARE, 'utf8');
    expect(content).toContain('/api/v1/auth/invites/:token');
    expect(content).toContain('/api/v1/auth/invites/:token/accept');
  });

  test('CSRF excludedPaths contains the DC-048 invite entry', () => {
    const content = fs.readFileSync(SRC_CSRF, 'utf8');
    expect(content).toContain('/api/v1/auth/invites/:token/accept');
  });

  // Behavioral test: the regex conversion that the middleware applies to a
  // `:param` entry should match real request paths. This exercises the SAME
  // algorithm used by `isPublicRoute()` in src/utilities/middleware.js and
  // `isExcluded` in src/security/csrf-protection.js, just in isolation.
  function _placeholderToRegex(p) {
    return '^' + p.replace(/:[A-Za-z_][A-Za-z0-9_]*/g, '[^/]+') + '$';
  }

  test('placeholder-to-regex algorithm matches share preview paths', () => {
    const pattern = _placeholderToRegex('/api/v1/share/:token/preview');
    expect(new RegExp(pattern).test('/api/v1/share/abc123/preview')).toBe(true);
    expect(new RegExp(pattern).test('/api/v1/share/some-very-long-token/preview')).toBe(true);
    // Different method/path segments should not match
    expect(new RegExp(pattern).test('/api/v1/share/abc/extra/preview')).toBe(false);
    expect(new RegExp(pattern).test('/api/v1/share/preview')).toBe(false);
  });

  test('placeholder-to-regex matches multi-param paths', () => {
    const pattern = _placeholderToRegex('/api/v1/auth/login/:provider/verify');
    expect(new RegExp(pattern).test('/api/v1/auth/login/totp/verify')).toBe(true);
    expect(new RegExp(pattern).test('/api/v1/auth/login/email/verify')).toBe(true);
    expect(new RegExp(pattern).test('/api/v1/auth/login/totp/initiate')).toBe(false);
  });

  test('placeholder-to-regex handles exact paths (no placeholders)', () => {
    const pattern = _placeholderToRegex('/health/live');
    expect(new RegExp(pattern).test('/health/live')).toBe(true);
    expect(new RegExp(pattern).test('/health/ready')).toBe(false);
  });

  test('placeholder-to-regex handles the auth/gate/ prefix exemption', () => {
    // /api/v1/auth/gate/ is a prefix match (not in PUBLIC_ROUTES entries
    // individually). Verify the algorithm preserves this by NOT requiring
    // placeholders when none are present.
    const pattern = _placeholderToRegex('/api/v1/auth/gate/foo');
    expect(new RegExp(pattern).test('/api/v1/auth/gate/foo')).toBe(true);
    expect(new RegExp(pattern).test('/api/v1/auth/gate/bar')).toBe(false);
  });
});