/**
 * Tests for the Remote Session Auth Gate middleware in webServer.js.
 *
 * When LOXIA_REMOTE_SESSION=true, every request (except /api/health and /health)
 * must carry a valid session token — either as a cookie or as a ?session_token= query param.
 *
 * Covers:
 *   - /api/health is always public (bypasses auth gate)
 *   - ?session_token= in URL sets cookie and redirects (strips token from URL)
 *   - Valid loxia_session cookie allows access
 *   - Missing/invalid token returns 401 JSON for /api/ paths
 *   - Missing/invalid token returns 401 HTML for non-API paths
 *   - Wrong token is rejected
 *   - API key auto-configuration from LOXIA_API_KEY env var
 */

import { describe, it, expect, beforeEach, afterEach, jest, beforeAll, afterAll } from '@jest/globals';
import express from 'express';
import { createServer } from 'http';

const VALID_TOKEN = 'test-session-token-abc123';

/**
 * Build a minimal Express app that replicates the remote session auth gate
 * from webServer.js, plus a few test routes behind it.
 */
function buildRemoteSessionApp(sessionToken) {
  const app = express();
  app.use(express.json());

  // ── Replicate the auth gate from webServer.js ──────────────────
  app.use((req, res, next) => {
    // Health endpoint is public
    if (req.path === '/api/health' || req.path === '/health') return next();

    // Static assets are public (CSS, JS, images, fonts)
    if (req.path.startsWith('/static/') || req.path.endsWith('.svg') || req.path.endsWith('.png') || req.path.endsWith('.ico') || req.path.endsWith('.woff2')) return next();

    // Parse cookies manually
    const cookies = {};
    (req.headers.cookie || '').split(';').forEach(c => {
      const [k, ...v] = c.trim().split('=');
      if (k) cookies[k] = v.join('=');
    });
    const tokenFromCookie = cookies.loxia_session;
    const tokenFromQuery = req.query?.session_token;

    if (tokenFromQuery === sessionToken) {
      // Set cookie and redirect without token in URL
      res.cookie('loxia_session', sessionToken, {
        httpOnly: true,
        sameSite: 'lax',
        maxAge: 7200 * 1000,
        path: '/'
      });
      const url = new URL(req.originalUrl, `http://${req.headers.host}`);
      url.searchParams.delete('session_token');
      return res.redirect(url.pathname + url.search);
    }

    if (tokenFromCookie === sessionToken) {
      return next();
    }

    // Unauthorized
    if (req.path.startsWith('/api/')) {
      return res.status(401).json({ error: 'Session authentication required' });
    }
    return res.status(401).send('<html><body><h2>Session Expired</h2></body></html>');
  });

  // ── Test routes behind the gate ────────────────────────────────
  app.get('/api/health', (req, res) => res.json({ status: 'healthy' }));
  app.get('/health', (req, res) => res.json({ status: 'healthy' }));
  app.get('/static/index-abc123.js', (req, res) => res.type('js').send('console.log("app")'));
  app.get('/static/index-abc123.css', (req, res) => res.type('css').send('body{}'));
  app.get('/loxia-icon.svg', (req, res) => res.type('svg').send('<svg></svg>'));
  app.get('/favicon.ico', (req, res) => res.type('ico').send(''));
  app.get('/api/test', (req, res) => res.json({ success: true, data: 'protected' }));
  app.get('/', (req, res) => res.send('<html><body>Web UI</body></html>'));
  app.get('/dashboard', (req, res) => res.send('<html><body>Dashboard</body></html>'));

  return app;
}

async function startServer(app) {
  const server = createServer(app);
  await new Promise(resolve => server.listen(0, resolve));
  return { server, baseUrl: `http://127.0.0.1:${server.address().port}` };
}

describe('Remote Session Auth Gate', () => {
  let server;

  afterEach(() => {
    if (server) server.close();
    server = null;
  });

  // ── Health endpoints bypass ──────────────────────────────────────
  describe('health endpoints (public)', () => {
    it('/api/health is accessible without any token', async () => {
      const app = buildRemoteSessionApp(VALID_TOKEN);
      const { server: s, baseUrl } = await startServer(app);
      server = s;

      const res = await fetch(`${baseUrl}/api/health`);
      expect(res.status).toBe(200);
      const body = await res.json();
      expect(body.status).toBe('healthy');
    });

    it('/health is accessible without any token', async () => {
      const app = buildRemoteSessionApp(VALID_TOKEN);
      const { server: s, baseUrl } = await startServer(app);
      server = s;

      const res = await fetch(`${baseUrl}/health`);
      expect(res.status).toBe(200);
    });
  });

  // ── Static assets bypass (public) ──────────────────────────────────
  describe('static assets (public, no auth needed)', () => {
    it('/static/*.js is accessible without any token', async () => {
      const app = buildRemoteSessionApp(VALID_TOKEN);
      const { server: s, baseUrl } = await startServer(app);
      server = s;

      const res = await fetch(`${baseUrl}/static/index-abc123.js`);
      expect(res.status).toBe(200);
    });

    it('/static/*.css is accessible without any token', async () => {
      const app = buildRemoteSessionApp(VALID_TOKEN);
      const { server: s, baseUrl } = await startServer(app);
      server = s;

      const res = await fetch(`${baseUrl}/static/index-abc123.css`);
      expect(res.status).toBe(200);
    });

    it('.svg files are accessible without any token', async () => {
      const app = buildRemoteSessionApp(VALID_TOKEN);
      const { server: s, baseUrl } = await startServer(app);
      server = s;

      const res = await fetch(`${baseUrl}/loxia-icon.svg`);
      expect(res.status).toBe(200);
    });

    it('.ico files are accessible without any token', async () => {
      const app = buildRemoteSessionApp(VALID_TOKEN);
      const { server: s, baseUrl } = await startServer(app);
      server = s;

      const res = await fetch(`${baseUrl}/favicon.ico`);
      expect(res.status).toBe(200);
    });
  });

  // ── Token via query param (first access) ─────────────────────────
  describe('?session_token= query param (first access)', () => {
    it('valid token sets cookie and redirects (strips token from URL)', async () => {
      const app = buildRemoteSessionApp(VALID_TOKEN);
      const { server: s, baseUrl } = await startServer(app);
      server = s;

      const res = await fetch(`${baseUrl}/?session_token=${VALID_TOKEN}`, { redirect: 'manual' });
      expect(res.status).toBe(302);

      // Check redirect strips the session_token param
      const location = res.headers.get('location');
      expect(location).toBe('/');
      expect(location).not.toContain('session_token');

      // Check Set-Cookie header
      const setCookie = res.headers.get('set-cookie');
      expect(setCookie).toContain('loxia_session=');
      expect(setCookie).toContain(VALID_TOKEN);
      expect(setCookie).toContain('HttpOnly');
    });

    it('preserves other query params when stripping session_token', async () => {
      const app = buildRemoteSessionApp(VALID_TOKEN);
      const { server: s, baseUrl } = await startServer(app);
      server = s;

      const res = await fetch(`${baseUrl}/dashboard?session_token=${VALID_TOKEN}&theme=dark`, { redirect: 'manual' });
      expect(res.status).toBe(302);
      const location = res.headers.get('location');
      expect(location).toContain('theme=dark');
      expect(location).not.toContain('session_token');
    });

    it('wrong token in query param is rejected (not redirected)', async () => {
      const app = buildRemoteSessionApp(VALID_TOKEN);
      const { server: s, baseUrl } = await startServer(app);
      server = s;

      const res = await fetch(`${baseUrl}/?session_token=wrong-token`, { redirect: 'manual' });
      // Should NOT redirect — falls through to 401
      expect(res.status).toBe(401);
    });
  });

  // ── Token via cookie (subsequent requests) ───────────────────────
  describe('loxia_session cookie (subsequent requests)', () => {
    it('valid cookie allows access to API endpoints', async () => {
      const app = buildRemoteSessionApp(VALID_TOKEN);
      const { server: s, baseUrl } = await startServer(app);
      server = s;

      const res = await fetch(`${baseUrl}/api/test`, {
        headers: { 'Cookie': `loxia_session=${VALID_TOKEN}` }
      });
      expect(res.status).toBe(200);
      const body = await res.json();
      expect(body.success).toBe(true);
      expect(body.data).toBe('protected');
    });

    it('valid cookie allows access to HTML pages', async () => {
      const app = buildRemoteSessionApp(VALID_TOKEN);
      const { server: s, baseUrl } = await startServer(app);
      server = s;

      const res = await fetch(`${baseUrl}/`, {
        headers: { 'Cookie': `loxia_session=${VALID_TOKEN}` }
      });
      expect(res.status).toBe(200);
      const text = await res.text();
      expect(text).toContain('Web UI');
    });

    it('wrong cookie value is rejected', async () => {
      const app = buildRemoteSessionApp(VALID_TOKEN);
      const { server: s, baseUrl } = await startServer(app);
      server = s;

      const res = await fetch(`${baseUrl}/api/test`, {
        headers: { 'Cookie': `loxia_session=wrong-value` }
      });
      expect(res.status).toBe(401);
    });
  });

  // ── No token at all ──────────────────────────────────────────────
  describe('no token (unauthenticated)', () => {
    it('API endpoints return 401 JSON', async () => {
      const app = buildRemoteSessionApp(VALID_TOKEN);
      const { server: s, baseUrl } = await startServer(app);
      server = s;

      const res = await fetch(`${baseUrl}/api/test`);
      expect(res.status).toBe(401);
      const body = await res.json();
      expect(body.error).toBe('Session authentication required');
    });

    it('HTML pages return 401 with Session Expired message', async () => {
      const app = buildRemoteSessionApp(VALID_TOKEN);
      const { server: s, baseUrl } = await startServer(app);
      server = s;

      const res = await fetch(`${baseUrl}/dashboard`);
      expect(res.status).toBe(401);
      const text = await res.text();
      expect(text).toContain('Session Expired');
    });

    it('root path without token returns 401', async () => {
      const app = buildRemoteSessionApp(VALID_TOKEN);
      const { server: s, baseUrl } = await startServer(app);
      server = s;

      const res = await fetch(`${baseUrl}/`);
      expect(res.status).toBe(401);
    });
  });

  // ── Cookie with multiple values ──────────────────────────────────
  describe('cookie parsing edge cases', () => {
    it('handles multiple cookies correctly (picks loxia_session)', async () => {
      const app = buildRemoteSessionApp(VALID_TOKEN);
      const { server: s, baseUrl } = await startServer(app);
      server = s;

      const res = await fetch(`${baseUrl}/api/test`, {
        headers: { 'Cookie': `other=foo; loxia_session=${VALID_TOKEN}; another=bar` }
      });
      expect(res.status).toBe(200);
    });

    it('handles cookie with = in value', async () => {
      // Token won't normally have = but test the parser handles it
      const tokenWithEquals = 'abc=def=ghi';
      const app = buildRemoteSessionApp(tokenWithEquals);
      const { server: s, baseUrl } = await startServer(app);
      server = s;

      const res = await fetch(`${baseUrl}/api/test`, {
        headers: { 'Cookie': `loxia_session=${tokenWithEquals}` }
      });
      expect(res.status).toBe(200);
    });
  });
});
