import 'dotenv/config';
import assert from 'node:assert/strict';
import { test } from 'node:test';
import request from 'supertest';
import argon2 from 'argon2';
import type { SessionData } from 'express-session';
import { createTestDatabase } from './helpers.js';
import { createPrismaClient } from '../../prisma/client.js';
import { PrismaSessionStore } from '../../server/session-store.js';
import { createApp } from '../../server/app.js';
import { passwordOptions } from '../../server/auth.js';

test('Prisma session storage and application restart', { timeout: 60_000 }, async t => {
  const database = await createTestDatabase('sessions');
  const { db, databaseUrl } = database;
  let store = new PrismaSessionStore(db);
  const value = (expires = new Date(Date.now() + 60_000)) => ({
    cookie: { expires, originalMaxAge: 60_000, httpOnly: true, secure: false, sameSite: 'lax', path: '/' },
    adminId: 123, authVersion: 1, csrfToken: 'a'.repeat(64),
  } as SessionData);
  const get = (sid: string) => new Promise<SessionData | null | undefined>((resolve, reject) => store.get(sid, (error, session) => error ? reject(error) : resolve(session)));
  const set = (sid: string, session: SessionData) => new Promise<void>((resolve, reject) => store.set(sid, session, error => error ? reject(error) : resolve()));
  const touch = (sid: string, session: SessionData) => new Promise<void>((resolve, reject) => store.touch(sid, session, error => error ? reject(error) : resolve()));
  const destroy = (sid: string) => new Promise<void>((resolve, reject) => store.destroy(sid, error => error ? reject(error) : resolve()));
  try {
    await t.test('session JSON, timestamps, and case-sensitive IDs round-trip', async () => {
      const session = value();
      await set('CaseSensitive', session);
      await set('casesensitive', { ...session, adminId: 456 });
      assert.equal((await get('CaseSensitive'))?.adminId, 123);
      assert.equal((await get('casesensitive'))?.adminId, 456);
      assert.equal((await db.authSession.findUniqueOrThrow({ where: { sid: 'CaseSensitive' } })).expire.toISOString(), session.cookie.expires!.toISOString());
      assert.equal(await get('missing'), null);
    });
    await t.test('touch refreshes only expiry without overwriting data or resurrecting sessions', async () => {
      const next = value(new Date(Date.now() + 120_000));
      next.adminId = 999;
      await touch('CaseSensitive', next);
      assert.equal((await get('CaseSensitive'))?.adminId, 123);
      assert.equal((await db.authSession.findUniqueOrThrow({ where: { sid: 'CaseSensitive' } })).expire.toISOString(), next.cookie.expires!.toISOString());
      await destroy('CaseSensitive');
      await touch('CaseSensitive', next);
      assert.equal(await get('CaseSensitive'), null);
      await set('expired', value(new Date(0)));
      await touch('expired', next);
      assert.equal(await get('expired'), null);
      assert.equal((await db.authSession.findUniqueOrThrow({ where: { sid: 'expired' } })).expire.getTime(), 0);
    });
    await t.test('pruning is shared by simultaneous calls and removes only expired sessions', async () => {
      const pruning = store.prune();
      assert.equal(store.prune(), pruning);
      await pruning;
      assert.equal(await db.authSession.count({ where: { sid: 'expired' } }), 0);
      assert.equal((await get('casesensitive'))?.adminId, 456);
      await store.close();
      await store.close();
      store = new PrismaSessionStore(db);
    });
    await t.test('authenticated cookie survives app/store recreation with a new Prisma connection', async () => {
      const password = 'restart-test-password-1234';
      const user = await db.adminUser.create({ data: { name: 'Restart Admin', email: 'restart@example.test', passwordHash: await argon2.hash(password, passwordOptions) } });
      const config = { databaseUrl, sessionSecret: 'restart-test-secret-with-at-least-32-characters', origin: 'http://localhost:3000' };
      const first = createApp(db, config);
      let cookie = '';
      try {
        const agent = request.agent(first.app);
        const csrfToken = (await agent.get('/api/auth/session')).body.csrfToken;
        const login = await agent.post('/api/auth/login').set('Origin', config.origin).set('X-CSRF-Token', csrfToken).send({ email: user.email, password });
        assert.equal(login.status, 200);
        cookie = String(login.headers['set-cookie'][0]).split(';')[0];
      } finally { await first.close(); }
      const reconnected = createPrismaClient(databaseUrl);
      const second = createApp(reconnected, config);
      try {
        const response = await request(second.app).get('/api/auth/session').set('Cookie', cookie);
        assert.equal(response.body.user.id, user.id);
        assert.equal((await request(second.app).get('/api/admin/events').set('Cookie', cookie)).status, 200);
        await db.adminUser.update({ where: { id: user.id }, data: { authVersion: { increment: 1 } } });
        assert.equal((await request(second.app).get('/api/admin/events').set('Cookie', cookie)).status, 401);
      } finally { await second.close(); await reconnected.$disconnect(); }
    });
  } finally { await store.close(); await database.close(); }
});
