'use strict';

const fs = require('fs');
const path = require('path');
const vm = require('vm');
const test = require('node:test');
const assert = require('node:assert/strict');

const source = fs.readFileSync(path.join(__dirname, '..', 'js', 'auth-gate.js'), 'utf8');
const totpSource = fs.readFileSync(path.join(__dirname, '..', 'js', 'totp-auth.js'), 'utf8');

function buildHandoffTarget(returnUrl, token, tld = '.sami') {
  const start = totpSource.indexOf('  function buildSsoHandoffTarget');
  const end = totpSource.indexOf('\n\n  // Setup digit input UX', start);
  assert.notEqual(start, -1, 'handoff builder must exist');
  assert.notEqual(end, -1, 'handoff builder boundary must exist');
  const functionSource = totpSource.slice(start, end);
  const context = {
    URL,
    SITE: { tld },
    window: { location: { origin: 'https://status.sami' } },
  };
  const sandbox = { ...context, input: returnUrl, token, result: undefined };
  vm.runInNewContext(`${functionSource}\nresult = buildSsoHandoffTarget(input, token);`, sandbox);
  return sandbox.result;
}

function capturedRedirect(returnUrl, tld = '.sami') {
  const stored = new Map();
  const query = new URLSearchParams({ auth: 'required', return: returnUrl });
  const location = {
    origin: 'https://status.sami',
    pathname: '/',
    search: `?${query.toString()}`,
    reload() {},
  };
  const context = {
    URL,
    URLSearchParams,
    SITE: { tld },
    sessionStorage: {
      setItem(key, value) { stored.set(key, value); },
    },
    document: { getElementById() { return null; } },
    setTimeout() {},
    console,
    window: {
      location,
      history: { replaceState() {} },
    },
  };
  context.window.window = context.window;
  vm.runInNewContext(source, context, { filename: 'auth-gate.js' });
  return stored.get('totp_redirect');
}

test('preserves a return URL on another host under the configured private TLD', () => {
  assert.equal(capturedRedirect('https://plex.sami/web/'), 'https://plex.sami/web/');
});

test('preserves a same-origin return URL', () => {
  assert.equal(capturedRedirect('https://status.sami/settings'), 'https://status.sami/settings');
});

test('rejects lookalike domains, plaintext cross-host URLs, and non-web schemes', () => {
  assert.equal(capturedRedirect('https://plex.sami.evil.example/'), undefined);
  assert.equal(capturedRedirect('http://plex.sami/'), undefined);
  assert.equal(capturedRedirect('javascript:alert(1)'), undefined);
});

test('accepts relative same-origin paths and protocol-relative HTTPS private hosts', () => {
  assert.equal(capturedRedirect('/settings'), '/settings');
  assert.equal(capturedRedirect('//plex.sami/web/'), '//plex.sami/web/');
});

test('normalizes a configured TLD without a leading dot', () => {
  assert.equal(capturedRedirect('https://plex.sami/web/', 'sami'), 'https://plex.sami/web/');
});

test('builds the generic cross-host SSO landing URL and preserves the final path', () => {
  assert.equal(
    buildHandoffTarget('https://router.sami/config?tab=network#dns', 'one-time'),
    'https://router.sami/dashcaddy-sso?token=one-time&return=%2Fconfig%3Ftab%3Dnetwork%23dns',
  );
});

test('does not create cross-host handoffs for plaintext or lookalike destinations', () => {
  assert.equal(buildHandoffTarget('http://router.sami/', 'one-time'), null);
  assert.equal(buildHandoffTarget('https://router.sami.evil.example/', 'one-time'), null);
});

test('same-origin and tokenless destinations keep their direct URL', () => {
  assert.equal(buildHandoffTarget('/settings', 'one-time'), 'https://status.sami/settings');
  assert.equal(buildHandoffTarget('https://router.sami/config', ''), 'https://router.sami/config');
});

test('an existing status.sami session returns to a service without another TOTP prompt', async () => {
  const query = new URLSearchParams({ auth: 'required', return: 'https://plex.sami/web/' });
  let scheduled;
  let redirected;
  const location = {
    origin: 'https://status.sami',
    pathname: '/',
    search: `?${query.toString()}`,
    replace(value) { redirected = value; },
  };
  const context = {
    URL,
    URLSearchParams,
    SITE: { tld: '.sami' },
    sessionStorage: { setItem() {} },
    document: { getElementById() { return null; } },
    setTimeout(fn) { scheduled = fn; },
    console,
    fetch: async (url) => {
      assert.equal(url, '/api/v1/auth/sso-handoff?serviceId=plex');
      return { ok: true, json: async () => ({ success: true, ssoToken: 'existing-session-token' }) };
    },
    window: {
      location,
      history: { replaceState() {} },
    },
  };
  context.window.window = context.window;
  vm.runInNewContext(source, context, { filename: 'auth-gate.js' });

  assert.equal(typeof scheduled, 'function');
  await scheduled();
  assert.equal(
    redirected,
    'https://plex.sami/dashcaddy-sso?token=existing-session-token&return=%2Fweb%2F',
  );
});
