import {
  BadRequestException,
  ForbiddenException,
  GoneException,
  HttpException,
  UnauthorizedException,
} from '@nestjs/common';
import { hashHighEntropyValue, hashValue } from '../../common/utils/hash.util';
import { AuthService } from './auth.service';

type MockMap = Record<string, jest.Mock>;

const future = () => new Date(Date.now() + 60_000);

const createHarness = (options: {
  config?: Record<string, unknown>;
  users?: Partial<MockMap>;
  repository?: Partial<MockMap>;
  jwt?: Partial<MockMap>;
  email?: Partial<MockMap>;
} = {}) => {
  const user = {
    id: 'user-1',
    email: 'user@example.com',
    username: 'artist',
    role: 'user',
    password: '',
    isDisabled: false,
    isEmailVerified: false,
    googleId: 'google-1',
    toObject: () => ({ id: 'user-1', email: 'user@example.com', username: 'artist' }),
  };
  const users: MockMap = {
    create: jest.fn().mockResolvedValue(user),
    createWithOptionalAvatarFile: jest.fn().mockResolvedValue(user),
    rollbackSignupCreatedUser: jest.fn().mockResolvedValue(undefined),
    findByEmailWithPassword: jest.fn(),
    findByIdWithPassword: jest.fn(),
    findByIdOrFail: jest.fn().mockResolvedValue(user),
    findByEmail: jest.fn().mockResolvedValue(null),
    findByUsername: jest.fn().mockResolvedValue(null),
    findByGoogleId: jest.fn().mockResolvedValue(null),
    linkGoogleAccount: jest.fn().mockResolvedValue(user),
    markEmailVerified: jest.fn().mockResolvedValue(undefined),
    updatePassword: jest.fn().mockResolvedValue(undefined),
    ...options.users,
  };
  const repository: MockMap = {
    invalidateActiveEmailVerificationCodes: jest.fn().mockResolvedValue(undefined),
    createEmailVerificationCode: jest.fn().mockResolvedValue(undefined),
    findLatestActiveEmailVerificationCode: jest.fn(),
    incrementEmailVerificationAttempts: jest.fn().mockResolvedValue(undefined),
    markEmailVerificationCodeUsed: jest.fn().mockResolvedValue(undefined),
    markAllEmailVerificationCodesUsedByUser: jest.fn().mockResolvedValue(undefined),
    createRefreshToken: jest.fn().mockResolvedValue(undefined),
    findActiveUserTokenByJti: jest.fn(),
    consumeUserTokenByJti: jest.fn().mockResolvedValue(true),
    markCompromisedAndRevokeAll: jest.fn().mockResolvedValue(undefined),
    revokeAllUserTokens: jest.fn().mockResolvedValue(undefined),
    revokeUserTokenByJti: jest.fn().mockResolvedValue(undefined),
    removeExpiredAndRevoked: jest.fn().mockResolvedValue(undefined),
    createSuperAdminRefreshToken: jest.fn().mockResolvedValue(undefined),
    findActiveSuperAdminTokenByJti: jest.fn(),
    consumeSuperAdminTokenByJti: jest.fn().mockResolvedValue(true),
    revokeAllSuperAdminTokens: jest.fn().mockResolvedValue(undefined),
    revokeSuperAdminTokenByJti: jest.fn().mockResolvedValue(undefined),
    removeExpiredAndRevokedSuperAdmin: jest.fn().mockResolvedValue(undefined),
    listUserSessions: jest.fn().mockResolvedValue([]),
    listSuperAdminSessions: jest.fn().mockResolvedValue([]),
    revokeSuperAdminSessionById: jest.fn().mockResolvedValue(true),
    invalidateActivePasswordResetCodes: jest.fn().mockResolvedValue(undefined),
    createPasswordResetCode: jest.fn().mockResolvedValue(undefined),
    findLatestActivePasswordResetCode: jest.fn(),
    incrementPasswordResetAttempts: jest.fn().mockResolvedValue(undefined),
    markPasswordResetCodeVerified: jest.fn().mockResolvedValue(undefined),
    markPasswordResetCodeUsed: jest.fn().mockResolvedValue(undefined),
    markPasswordResetCodeUsedByUser: jest.fn().mockResolvedValue(undefined),
    consumeValidVerifiedPasswordResetCode: jest.fn(),
    ...options.repository,
  };
  const jwt: MockMap = {
    signAsync: jest.fn((payload: { tokenType?: string }) =>
      Promise.resolve(payload.tokenType?.includes('refresh') ? 'new-refresh-token' : 'new-access-token'),
    ),
    verify: jest.fn(),
    ...options.jwt,
  };
  const email: MockMap = {
    sendVerificationCode: jest.fn().mockResolvedValue(undefined),
    sendPasswordResetCode: jest.fn().mockResolvedValue(undefined),
    ...options.email,
  };
  const config: Record<string, unknown> = {
    nodeEnv: 'test',
    'security.bcryptSaltRounds': 4,
    'security.refreshTokenHashSecret': 'hash-secret',
    'jwt.accessSecret': 'access-secret',
    'jwt.accessExpiresIn': '15m',
    'jwt.refreshSecret': 'refresh-secret',
    'jwt.refreshExpiresIn': '30d',
    'superAdmin.email': 'root@example.com',
    'superAdmin.password': 'RootPass123!',
    'superAdmin.accessSecret': 'admin-access-secret',
    'superAdmin.accessExpiresIn': '15m',
    'superAdmin.refreshSecret': 'admin-refresh-secret',
    'superAdmin.refreshExpiresIn': '30d',
    'emailVerification.codeExpiresMinutes': 20,
    'emailVerification.maxAttempts': 3,
    'passwordReset.codeExpiresMinutes': 10,
    'passwordReset.maxAttempts': 3,
    'passwordReset.tokenSecret': 'password-reset-secret',
    'passwordReset.tokenExpiresIn': '15m',
    'google.clientId': 'google-client-id',
    ...options.config,
  };
  const configService = {
    get: jest.fn((key: string) => config[key]),
  };
  const service = new AuthService(
    users as any,
    repository as any,
    jwt as any,
    configService as any,
    email as any,
  );

  return { service, users, repository, jwt, email, configService, user };
};

describe('AuthService security-sensitive account flows', () => {
  describe('registration and login', () => {
    it('rejects registration when password confirmation differs', async () => {
      const { service, users } = createHarness();

      await expect(
        service.register({
          email: 'user@example.com',
          username: 'artist',
          password: 'StrongPass123!',
          confirmPassword: 'DifferentPass123!',
        } as any),
      ).rejects.toBeInstanceOf(BadRequestException);
      expect(users.create).not.toHaveBeenCalled();
    });

    it('normalizes a generated unique username and never stores the confirmation', async () => {
      const { service, users, repository, email } = createHarness({
        users: {
          findByUsername: jest
            .fn()
            .mockResolvedValueOnce({ id: 'collision' })
            .mockResolvedValueOnce(null),
        },
      });

      const result = await service.register({
        email: 'A+rtist@example.com',
        stageName: 'Stage Artist',
        password: 'StrongPass123!',
        confirmPassword: 'StrongPass123!',
      } as any);

      expect(users.create).toHaveBeenCalledWith(
        expect.objectContaining({ username: 'artist_1', name: 'Stage Artist' }),
      );
      expect(users.create.mock.calls[0][0]).not.toHaveProperty('confirmPassword');
      expect(repository.invalidateActiveEmailVerificationCodes).toHaveBeenCalledWith('user-1');
      expect(email.sendVerificationCode).toHaveBeenCalled();
      expect(result.debugCode).toMatch(/^\d{6}$/);
    });

    it('omits verification codes in production registration responses', async () => {
      const { service } = createHarness({ config: { nodeEnv: 'production' } });

      const result = await service.registerBasic({
        email: 'new@example.com',
        password: 'StrongPass123!',
        confirmPassword: 'StrongPass123!',
      });

      expect(result).toEqual({
        message: 'Registration successful. Verification code sent to email.',
        email: 'user@example.com',
      });
    });

    it('rejects a missing account, disabled account, and incorrect password', async () => {
      const missing = createHarness();
      await expect(
        missing.service.login({ email: 'none@example.com', password: 'StrongPass123!' }),
      ).rejects.toBeInstanceOf(UnauthorizedException);

      const disabledHash = await hashValue('StrongPass123!', 4);
      const disabled = createHarness({
        users: {
          findByEmailWithPassword: jest.fn().mockResolvedValue({
            ...missing.user,
            password: disabledHash,
            isDisabled: true,
          }),
        },
      });
      await expect(
        disabled.service.login({ email: 'user@example.com', password: 'StrongPass123!' }),
      ).rejects.toBeInstanceOf(ForbiddenException);

      const wrong = createHarness({
        users: {
          findByEmailWithPassword: jest.fn().mockResolvedValue({
            ...missing.user,
            password: disabledHash,
          }),
        },
      });
      await expect(
        wrong.service.login({ email: 'user@example.com', password: 'WrongPass123!' }),
      ).rejects.toBeInstanceOf(UnauthorizedException);
    });

    it('returns a safe user and persists a hashed refresh token on login', async () => {
      const password = await hashValue('StrongPass123!', 4);
      const { service, users, repository } = createHarness({
        users: {
          findByEmailWithPassword: jest.fn().mockResolvedValue({
            id: 'user-1',
            username: 'artist',
            role: undefined,
            password,
            isDisabled: false,
          }),
        },
      });

      const result = await service.login({
        email: 'user@example.com',
        password: 'StrongPass123!',
      });

      expect(result).toMatchObject({ accessToken: 'new-access-token', refreshToken: 'new-refresh-token' });
      expect(users.findByIdOrFail).toHaveBeenCalledWith('user-1');
      expect(repository.createRefreshToken).toHaveBeenCalledWith(
        'user-1',
        expect.any(String),
        hashHighEntropyValue('new-refresh-token', 'hash-secret'),
        expect.any(Date),
      );
    });
  });

  describe('email verification', () => {
    it('does not reveal whether an email is absent or disabled', async () => {
      const absent = createHarness();
      const disabled = createHarness({
        users: { findByEmail: jest.fn().mockResolvedValue({ isDisabled: true }) },
      });

      await expect(absent.service.sendEmailVerification({ email: 'NONE@EXAMPLE.COM' })).resolves.toEqual(
        { message: 'If this email exists, a verification code was sent' },
      );
      await expect(disabled.service.sendEmailVerification({ email: 'user@example.com' })).resolves.toEqual(
        { message: 'If this email exists, a verification code was sent' },
      );
      expect(absent.repository.createEmailVerificationCode).not.toHaveBeenCalled();
    });

    it('short-circuits an already verified account', async () => {
      const { service, repository } = createHarness({
        users: { findByEmail: jest.fn().mockResolvedValue({ isEmailVerified: true }) },
      });

      await expect(service.sendEmailVerification({ email: 'user@example.com' })).resolves.toEqual({
        message: 'Email is already verified',
      });
      expect(repository.createEmailVerificationCode).not.toHaveBeenCalled();
    });

    it('invalidates a code at the attempt limit', async () => {
      const { service, repository } = createHarness({
        users: { findByEmail: jest.fn().mockResolvedValue({ id: 'user-1', isDisabled: false }) },
        repository: {
          findLatestActiveEmailVerificationCode: jest.fn().mockResolvedValue({
            id: 'code-1',
            attempts: 3,
          }),
        },
      });

      await expect(
        service.verifyEmail({ email: 'user@example.com', code: '123456' }),
      ).rejects.toBeInstanceOf(UnauthorizedException);
      expect(repository.markEmailVerificationCodeUsed).toHaveBeenCalledWith('code-1');
    });

    it('increments a wrong-code attempt and consumes the final attempt', async () => {
      const codeHash = await hashValue('123456', 4);
      const { service, repository } = createHarness({
        users: { findByEmail: jest.fn().mockResolvedValue({ id: 'user-1', isDisabled: false }) },
        repository: {
          findLatestActiveEmailVerificationCode: jest.fn().mockResolvedValue({
            id: 'code-1',
            codeHash,
            attempts: 2,
          }),
        },
      });

      await expect(
        service.verifyEmail({ email: 'user@example.com', code: '654321' }),
      ).rejects.toBeInstanceOf(UnauthorizedException);
      expect(repository.incrementEmailVerificationAttempts).toHaveBeenCalledWith('code-1');
      expect(repository.markEmailVerificationCodeUsed).toHaveBeenCalledWith('code-1');
    });

    it('atomically verifies the user and invalidates every outstanding code', async () => {
      const codeHash = await hashValue('123456', 4);
      const { service, users, repository } = createHarness({
        users: { findByEmail: jest.fn().mockResolvedValue({ id: 'user-1', isDisabled: false }) },
        repository: {
          findLatestActiveEmailVerificationCode: jest.fn().mockResolvedValue({
            id: 'code-1',
            codeHash,
            attempts: 0,
          }),
        },
      });

      await expect(
        service.verifyEmail({ email: 'USER@EXAMPLE.COM', code: '123456' }),
      ).resolves.toEqual({ message: 'Email verified successfully' });
      expect(users.markEmailVerified).toHaveBeenCalledWith('user-1');
      expect(repository.markAllEmailVerificationCodesUsedByUser).toHaveBeenCalledWith('user-1');
    });
  });

  describe('refresh-token rotation', () => {
    it('rejects access tokens passed as refresh tokens before repository lookup', async () => {
      const { service, repository } = createHarness({
        jwt: { verify: jest.fn().mockReturnValue({ sub: 'user-1', tokenType: 'access' }) },
      });

      await expect(service.refresh({ refreshToken: 'token' })).rejects.toBeInstanceOf(
        UnauthorizedException,
      );
      expect(repository.findActiveUserTokenByJti).not.toHaveBeenCalled();
    });

    it.each([
      ['missing record', null, true],
      ['hash mismatch', { tokenHash: hashHighEntropyValue('other-token', 'hash-secret') }, true],
    ])('revokes all sessions on %s', async (_name, tokenRecord, consume) => {
      const { service, repository } = createHarness({
        jwt: {
          verify: jest.fn().mockReturnValue({ sub: 'user-1', tokenType: 'refresh', jti: 'jti-1' }),
        },
        repository: {
          findActiveUserTokenByJti: jest.fn().mockResolvedValue(tokenRecord),
          consumeUserTokenByJti: jest.fn().mockResolvedValue(consume),
        },
      });

      await expect(service.refresh({ refreshToken: 'refresh-token' })).rejects.toBeInstanceOf(
        UnauthorizedException,
      );
      expect(repository.markCompromisedAndRevokeAll).toHaveBeenCalledWith('user-1');
    });

    it('detects a concurrent double-use when token consumption loses the race', async () => {
      const raw = 'refresh-token';
      const { service, repository } = createHarness({
        jwt: {
          verify: jest.fn().mockReturnValue({ sub: 'user-1', tokenType: 'refresh', jti: 'jti-1' }),
        },
        repository: {
          findActiveUserTokenByJti: jest.fn().mockResolvedValue({
            tokenHash: hashHighEntropyValue(raw, 'hash-secret'),
          }),
          consumeUserTokenByJti: jest.fn().mockResolvedValue(false),
        },
      });

      await expect(service.refresh({ refreshToken: raw })).rejects.toBeInstanceOf(
        UnauthorizedException,
      );
      expect(repository.markCompromisedAndRevokeAll).toHaveBeenCalled();
    });

    it('blocks rotation for a disabled user', async () => {
      const raw = 'refresh-token';
      const { service, repository } = createHarness({
        jwt: {
          verify: jest.fn().mockReturnValue({ sub: 'user-1', tokenType: 'refresh', jti: 'jti-1' }),
        },
        users: { findByIdOrFail: jest.fn().mockResolvedValue({ isDisabled: true }) },
        repository: {
          findActiveUserTokenByJti: jest.fn().mockResolvedValue({
            tokenHash: hashHighEntropyValue(raw, 'hash-secret'),
          }),
        },
      });

      await expect(service.refresh({ refreshToken: raw })).rejects.toBeInstanceOf(
        ForbiddenException,
      );
      expect(repository.consumeUserTokenByJti).not.toHaveBeenCalled();
    });

    it('consumes the old token before issuing a new pair', async () => {
      const raw = 'refresh-token';
      const { service, repository } = createHarness({
        jwt: {
          verify: jest.fn().mockReturnValue({ sub: 'user-1', tokenType: 'refresh', jti: 'jti-1' }),
        },
        repository: {
          findActiveUserTokenByJti: jest.fn().mockResolvedValue({
            tokenHash: hashHighEntropyValue(raw, 'hash-secret'),
          }),
        },
      });

      await expect(service.refresh({ refreshToken: raw })).resolves.toMatchObject({
        accessToken: 'new-access-token',
        refreshToken: 'new-refresh-token',
      });
      expect(repository.consumeUserTokenByJti).toHaveBeenCalledWith('user-1', 'jti-1');
    });

    it('revokes and cleans sessions at logout but maps invalid tokens to 400', async () => {
      const valid = createHarness({ jwt: { verify: jest.fn().mockReturnValue({ sub: 'user-1' }) } });
      await expect(valid.service.logout({ refreshToken: 'valid' })).resolves.toBeUndefined();
      expect(valid.repository.revokeAllUserTokens).toHaveBeenCalledWith('user-1');

      const invalid = createHarness({ jwt: { verify: jest.fn(() => { throw new Error('bad'); }) } });
      await expect(invalid.service.logout({ refreshToken: 'bad' })).rejects.toBeInstanceOf(
        BadRequestException,
      );
    });
  });

  describe('Google and superadmin authentication', () => {
    it('creates and links a new Google user while defaulting a missing avatar', async () => {
      const { service, users } = createHarness({
        users: {
          create: jest.fn().mockResolvedValue({
            id: 'user-1',
            username: 'new',
            role: 'user',
            isDisabled: false,
          }),
        },
      });

      const result = await service.loginWithGoogle({
        googleId: 'google-new',
        email: 'new@example.com',
        name: 'New User',
      });

      expect(users.create).toHaveBeenCalledWith(
        expect.objectContaining({
          email: 'new@example.com',
          avatar: '',
          isEmailVerified: true,
        }),
      );
      expect(users.linkGoogleAccount).toHaveBeenCalled();
      expect(result.accessToken).toBe('new-access-token');
    });

    it('rejects disabled Google-linked accounts', async () => {
      const { service } = createHarness({
        users: {
          findByGoogleId: jest.fn().mockResolvedValue({
            id: 'user-1',
            googleId: 'google-1',
            isDisabled: true,
          }),
        },
      });

      await expect(
        service.loginWithGoogle({
          googleId: 'google-1',
          email: 'user@example.com',
          name: 'User',
        }),
      ).rejects.toBeInstanceOf(ForbiddenException);
    });

    it('rejects Google ID login when the client or verified claims are missing', async () => {
      const missingClient = createHarness({ config: { 'google.clientId': ' ' } });
      await expect(
        missingClient.service.loginWithGoogleIdToken({ idToken: 'token' }),
      ).rejects.toBeInstanceOf(BadRequestException);

      const invalidClaims = createHarness();
      (invalidClaims.service as any).googleOAuthClient = {
        verifyIdToken: jest.fn().mockResolvedValue({
          getPayload: () => ({ sub: 'google-1', email: 'user@example.com', email_verified: false }),
        }),
      };
      await expect(
        invalidClaims.service.loginWithGoogleIdToken({ idToken: 'token' }),
      ).rejects.toBeInstanceOf(UnauthorizedException);
    });

    it('rejects wrong superadmin credentials and requires OTP when configured', async () => {
      const wrong = createHarness();
      await expect(
        wrong.service.superAdminLogin({ email: 'root@example.com', password: 'wrong' }),
      ).rejects.toBeInstanceOf(UnauthorizedException);

      const otpRequired = createHarness({ config: { 'superAdmin.totpSecret': 'JBSWY3DPEHPK3PXP' } });
      await expect(
        otpRequired.service.superAdminLogin({
          email: 'root@example.com',
          password: 'RootPass123!',
        }),
      ).rejects.toBeInstanceOf(UnauthorizedException);
    });

    it('issues dedicated superadmin tokens and persists only the refresh hash', async () => {
      const { service, repository, jwt } = createHarness();

      const result = await service.superAdminLogin({
        email: 'ROOT@example.com',
        password: 'RootPass123!',
      });

      expect(result.superAdmin.email).toBe('root@example.com');
      expect(jwt.signAsync).toHaveBeenCalledWith(
        expect.objectContaining({ tokenType: 'superadmin_access', permissions: expect.any(Array) }),
        expect.any(Object),
      );
      expect(repository.createSuperAdminRefreshToken).toHaveBeenCalledWith(
        'root@example.com',
        expect.any(String),
        hashHighEntropyValue('new-refresh-token', 'hash-secret'),
        expect.any(Date),
      );
    });

    it('detects missing, mismatched, and concurrently consumed superadmin refresh tokens', async () => {
      const cases = [
        { record: null, consumed: true },
        { record: { tokenHash: hashHighEntropyValue('other', 'hash-secret') }, consumed: true },
        { record: { tokenHash: hashHighEntropyValue('admin-refresh', 'hash-secret') }, consumed: false },
      ];

      for (const testCase of cases) {
        const { service, repository } = createHarness({
          jwt: {
            verify: jest.fn().mockReturnValue({
              email: 'root@example.com',
              tokenType: 'superadmin_refresh',
              jti: 'admin-jti',
            }),
          },
          repository: {
            findActiveSuperAdminTokenByJti: jest.fn().mockResolvedValue(testCase.record),
            consumeSuperAdminTokenByJti: jest.fn().mockResolvedValue(testCase.consumed),
          },
        });

        await expect(
          service.superAdminRefresh({ refreshToken: 'admin-refresh' }),
        ).rejects.toBeInstanceOf(UnauthorizedException);
        expect(repository.revokeAllSuperAdminTokens).toHaveBeenCalledWith('root@example.com');
      }
    });

    it('rotates a valid superadmin token and supports targeted/global logout', async () => {
      const raw = 'admin-refresh';
      const rotating = createHarness({
        jwt: {
          verify: jest.fn().mockReturnValue({
            email: 'root@example.com',
            tokenType: 'superadmin_refresh',
            jti: 'admin-jti',
          }),
        },
        repository: {
          findActiveSuperAdminTokenByJti: jest.fn().mockResolvedValue({
            tokenHash: hashHighEntropyValue(raw, 'hash-secret'),
          }),
        },
      });
      await expect(rotating.service.superAdminRefresh({ refreshToken: raw })).resolves.toMatchObject({
        superAdmin: { email: 'root@example.com' },
      });

      await rotating.service.superAdminLogout({ refreshToken: raw });
      expect(rotating.repository.revokeSuperAdminTokenByJti).toHaveBeenCalledWith(
        'root@example.com',
        'admin-jti',
      );

      const global = createHarness({
        jwt: { verify: jest.fn().mockReturnValue({ email: 'root@example.com' }) },
      });
      await global.service.superAdminLogout({ refreshToken: raw });
      expect(global.repository.revokeAllSuperAdminTokens).toHaveBeenCalledWith('root@example.com');
    });
  });

  describe('password reset and session management', () => {
    it('keeps forgot-password responses indistinguishable and omits production codes', async () => {
      const missing = createHarness();
      await expect(missing.service.forgotPassword({ email: 'NONE@EXAMPLE.COM' })).resolves.toEqual({
        message: 'If this email exists, a reset code was sent',
      });

      const production = createHarness({
        config: { nodeEnv: 'production' },
        users: { findByEmail: jest.fn().mockResolvedValue({ id: 'user-1', isDisabled: false }) },
      });
      const result = await production.service.forgotPassword({ email: 'USER@EXAMPLE.COM' });
      expect(result.debugCode).toBeUndefined();
      expect(production.repository.invalidateActivePasswordResetCodes).toHaveBeenCalledWith('user-1');
      expect(production.email.sendPasswordResetCode).toHaveBeenCalledWith(
        'user@example.com',
        expect.stringMatching(/^\d{6}$/),
        10,
      );
    });

    it('locks a reset code that already reached or reaches the attempt limit', async () => {
      for (const attempts of [3, 2]) {
        const codeHash = await hashValue('123456', 4);
        const { service, repository } = createHarness({
          users: { findByEmail: jest.fn().mockResolvedValue({ id: 'user-1', isDisabled: false }) },
          repository: {
            findLatestActivePasswordResetCode: jest.fn().mockResolvedValue({
              id: 'reset-1',
              attempts,
              codeHash,
            }),
          },
        });

        await expect(
          service.verifyResetCode({ email: 'user@example.com', code: '654321' }),
        ).rejects.toBeInstanceOf(UnauthorizedException);
        expect(repository.markPasswordResetCodeUsed).toHaveBeenCalledWith('reset-1');
      }
    });

    it('marks the code verified and issues a short-lived purpose-bound reset token', async () => {
      const codeHash = await hashValue('123456', 4);
      const { service, repository, jwt } = createHarness({
        users: { findByEmail: jest.fn().mockResolvedValue({ id: 'user-1', isDisabled: false }) },
        repository: {
          findLatestActivePasswordResetCode: jest.fn().mockResolvedValue({
            id: 'reset-1',
            attempts: 0,
            codeHash,
          }),
        },
      });

      await expect(
        service.verifyResetCode({ email: 'USER@EXAMPLE.COM', code: '123456' }),
      ).resolves.toEqual({ resetToken: 'new-access-token', expiresIn: '15m' });
      expect(repository.markPasswordResetCodeVerified).toHaveBeenCalledWith('reset-1');
      expect(jwt.signAsync).toHaveBeenCalledWith(
        { sub: 'user-1', tokenType: 'password_reset', prcId: 'reset-1' },
        expect.objectContaining({ secret: 'password-reset-secret' }),
      );
    });

    it('rejects reset confirmation mismatch, invalid JWTs, wrong purpose and consumed codes', async () => {
      const mismatch = createHarness();
      await expect(
        mismatch.service.resetPassword({
          resetToken: 'token',
          newPassword: 'StrongPass123!',
          confirmPassword: 'DifferentPass123!',
        }),
      ).rejects.toBeInstanceOf(BadRequestException);

      const invalid = createHarness({ jwt: { verify: jest.fn(() => { throw new Error('expired'); }) } });
      await expect(
        invalid.service.resetPassword({
          resetToken: 'token',
          newPassword: 'StrongPass123!',
          confirmPassword: 'StrongPass123!',
        }),
      ).rejects.toBeInstanceOf(UnauthorizedException);

      const wrongPurpose = createHarness({
        jwt: { verify: jest.fn().mockReturnValue({ sub: 'user-1', tokenType: 'access' }) },
      });
      await expect(
        wrongPurpose.service.resetPassword({
          resetToken: 'token',
          newPassword: 'StrongPass123!',
          confirmPassword: 'StrongPass123!',
        }),
      ).rejects.toBeInstanceOf(UnauthorizedException);

      const consumed = createHarness({
        jwt: {
          verify: jest.fn().mockReturnValue({
            sub: 'user-1',
            tokenType: 'password_reset',
            prcId: 'reset-1',
          }),
        },
      });
      await expect(
        consumed.service.resetPassword({
          resetToken: 'token',
          newPassword: 'StrongPass123!',
          confirmPassword: 'StrongPass123!',
        }),
      ).rejects.toBeInstanceOf(UnauthorizedException);
    });

    it('consumes a reset code exactly once then revokes all existing sessions', async () => {
      const { service, users, repository } = createHarness({
        jwt: {
          verify: jest.fn().mockReturnValue({
            sub: 'user-1',
            tokenType: 'password_reset',
            prcId: 'reset-1',
          }),
        },
        repository: {
          consumeValidVerifiedPasswordResetCode: jest.fn().mockResolvedValue({ id: 'reset-1' }),
        },
      });

      await expect(
        service.resetPassword({
          resetToken: 'token',
          newPassword: 'StrongPass123!',
          confirmPassword: 'StrongPass123!',
        }),
      ).resolves.toEqual({ message: 'Password reset successfully' });
      expect(users.updatePassword).toHaveBeenCalledWith('user-1', expect.any(String));
      expect(repository.revokeAllUserTokens).toHaveBeenCalledWith('user-1');
    });

    it('lists and revokes sessions without exposing token hashes', async () => {
      const createdAt = new Date();
      const expiresAt = future();
      const { service, repository } = createHarness({
        repository: {
          listUserSessions: jest.fn().mockResolvedValue([
            { jti: 'jti-1', tokenHash: 'secret', createdAt, expiresAt },
          ]),
          listSuperAdminSessions: jest.fn().mockResolvedValue([
            { id: 'session-1', jti: 'jti-2', tokenHash: 'secret', createdAt, expiresAt },
          ]),
        },
      });

      expect(await service.listUserSessions('user-1')).toEqual({
        items: [{ jti: 'jti-1', createdAt, expiresAt }],
      });
      expect(await service.listSuperAdminSessions('root@example.com')).toEqual({
        items: [{ id: 'session-1', jti: 'jti-2', createdAt, expiresAt }],
      });
      await service.revokeUserSession('user-1', 'jti-1');
      expect(repository.revokeUserTokenByJti).toHaveBeenCalledWith('user-1', 'jti-1');
    });

    it('fails closed when a requested superadmin session no longer exists', async () => {
      const { service } = createHarness({
        repository: { revokeSuperAdminSessionById: jest.fn().mockResolvedValue(false) },
      });

      await expect(
        service.revokeSuperAdminSession('root@example.com', 'missing'),
      ).rejects.toBeInstanceOf(BadRequestException);
    });
  });

  describe('signup draft hardening', () => {
    it('rejects consumed, mismatched and expired drafts', async () => {
      const invalidDrafts = [
        { consumed: true, email: 'user@example.com', expiresAt: future(), codeExpiresAt: future() },
        { consumed: false, email: 'other@example.com', expiresAt: future(), codeExpiresAt: future() },
      ];
      for (const draft of invalidDrafts) {
        const { service } = createHarness({
          repository: { findSignupDraftWithSecrets: jest.fn().mockResolvedValue(draft) },
        });
        await expect(
          service.signupVerify({ signupId: 'draft-1', email: 'user@example.com', code: '123456' }),
        ).rejects.toBeInstanceOf(BadRequestException);
      }

      const expired = createHarness({
        repository: {
          findSignupDraftWithSecrets: jest.fn().mockResolvedValue({
            consumed: false,
            email: 'user@example.com',
            expiresAt: new Date(Date.now() - 1),
            codeExpiresAt: future(),
          }),
        },
      });
      await expect(
        expired.service.signupVerify({
          signupId: 'draft-1',
          email: 'user@example.com',
          code: '123456',
        }),
      ).rejects.toBeInstanceOf(GoneException);
    });

    it('returns 429 before checking a draft code at the attempt limit', async () => {
      const { service } = createHarness({
        repository: {
          findSignupDraftWithSecrets: jest.fn().mockResolvedValue({
            id: 'draft-1',
            consumed: false,
            email: 'user@example.com',
            expiresAt: future(),
            codeExpiresAt: future(),
            attempts: 3,
          }),
        },
      });

      await expect(
        service.signupVerify({ signupId: 'draft-1', email: 'user@example.com', code: '123456' }),
      ).rejects.toBeInstanceOf(HttpException);
    });

    it('uses bounded expiration parsing and token masking helpers', () => {
      const { service } = createHarness();
      expect((service as any).parseExpiresInToMs('10s')).toBe(10_000);
      expect((service as any).parseExpiresInToMs('5m')).toBe(300_000);
      expect((service as any).parseExpiresInToMs('2h')).toBe(7_200_000);
      expect((service as any).parseExpiresInToMs('1d')).toBe(86_400_000);
      expect((service as any).parseExpiresInToMs('invalid')).toBe(2_592_000_000);
      expect((service as any).clampMinutes(Number.NaN, 5, 30)).toBe(5);
      expect((service as any).clampMinutes(99, 5, 30)).toBe(30);
      expect((service as any).maskToken('short')).toBe('[redacted]');
      expect((service as any).maskToken('1234567890abcdefghijkl')).toBe('12345678...efghijkl');
    });
  });
});
