import assert from 'node:assert/strict';
import { createHash } from 'node:crypto';
import { describe, it } from 'node:test';
import { AuthService } from './auth.service';
import { SessionGuard } from './session.guard';

describe('auth token hashing contract', () => {
  it('sha256 hex is stable 64 chars', () => {
    const hash = createHash('sha256').update('token').digest('hex');
    assert.equal(hash.length, 64);
  });
});

describe('effective permissions', () => {
  it('unions role permissions, applies ALLOW, then lets DENY win', async () => {
    const prisma = {
      user: {
        findUniqueOrThrow: async () => ({
          id: 'u1',
          organizationId: 'org1',
          username: 'user',
          fullName: 'User',
          email: null,
          isSuperAdmin: false,
          mustResetPassword: true,
          userRoles: [
            {
              role: {
                code: 'STAFF',
                isActive: true, archivedAt: null,
                rolePermissions: [{ permission: { code: 'orders.view' } }, { permission: { code: 'orders.edit' } }],
              },
            },
            {
              role: {
                code: 'REPORTER',
                isActive: true, archivedAt: null,
                rolePermissions: [{ permission: { code: 'reports.view' } }],
              },
            },
          ],
          permissionOverrides: [
            { effect: 'ALLOW', permission: { code: 'customers.view' } },
            { effect: 'DENY', permission: { code: 'orders.edit' } },
          ],
        }),
      },
    };
    const service = new AuthService(prisma as never, {} as never, {} as never);
    const user = await service.buildSessionUser('u1');

    assert.deepEqual(user.permissions, ['customers.view', 'orders.view', 'reports.view']);
    assert.equal(user.mustResetPassword, true);
  });
});

describe('forced password reset session restriction', () => {
  const forced = { id: 'u', organizationId: 'o', username: 'u', fullName: 'U', email: null, isSuperAdmin: false, mustResetPassword: true, permissions: [], roles: [] };
  const guard = new SessionGuard({ getAllAndOverride: () => false } as never, { resolveSession: async () => ({ sessionUser: forced, sessionId: 's' }) } as never);
  function context(path: string) {
    const req: any = { path, cookies: { tms_session: 't' } };
    return { switchToHttp: () => ({ getRequest: () => req }), getHandler: () => ({}), getClass: () => ({}) } as any;
  }
  it('allows only auth/me while reset is required', async () => {
    assert.equal(await guard.canActivate(context('/api/v1/auth/me')), true);
    await assert.rejects(() => guard.canActivate(context('/api/v1/users')), /Password must be changed/);
  });
});

describe('distributed session revocation', () => {
  it('rejects a cached session after another service instance revokes it', async () => {
    const rawToken = 'shared-session-token';
    const tokenHash = createHash('sha256').update(rawToken).digest('hex');
    const state: { revokedAt: Date | null } = { revokedAt: null };
    const user = {
      id: 'u1',
      organizationId: 'org1',
      username: 'user',
      fullName: 'User',
      email: null,
      isSuperAdmin: false,
      mustResetPassword: false,
      isActive: true,
      archivedAt: null,
      organization: { isActive: true, archivedAt: null },
      userRoles: [],
      permissionOverrides: [],
    };
    const prisma = {
      session: {
        findUnique: async ({ where }: { where: { tokenHash: string } }) =>
          where.tokenHash === tokenHash
            ? {
                id: 'session-1',
                userId: user.id,
                organizationId: user.organizationId,
                expiresAt: new Date(Date.now() + 60_000),
                revokedAt: state.revokedAt,
                user,
              }
            : null,
        updateMany: async () => {
          state.revokedAt = new Date();
          return { count: 1 };
        },
        update: async () => ({ id: 'session-1' }),
      },
      user: {
        findUniqueOrThrow: async () => user,
      },
    };
    const serviceA = new AuthService(prisma as never, {} as never, {} as never);
    const serviceB = new AuthService(prisma as never, {} as never, {} as never);

    assert.ok(await serviceB.resolveSession(rawToken));
    await serviceA.logoutAll(user.id, user.organizationId);
    assert.equal(await serviceB.resolveSession(rawToken), null);
  });
});