import { IamSequelizeUserRepository } from '@app/iam/user';
import { Test, TestingModule } from '@nestjs/testing';
import * as bcrypt from 'bcrypt';
import { IamFindUserByUsernamePasswordService } from './iam-find-user-by-username-password.service';

const KNOWN_USERNAME = 'carlos';
const KNOWN_PASSWORD = 'hunter2';
const HASHED_PASSWORD = bcrypt.hashSync(KNOWN_PASSWORD, 10);

const buildUser = (
  overrides: { password?: string; isActive?: boolean } = {},
): {
  id: string;
  accountId: string;
  password: string;
  account: { id: string; isActive: boolean; username: string };
} => ({
  id: 'u1',
  accountId: 'a1',
  password: overrides.password ?? HASHED_PASSWORD,
  account: {
    id: 'a1',
    username: KNOWN_USERNAME,
    isActive: overrides.isActive ?? true,
  },
});

describe('IamFindUserByUsernamePasswordService', () => {
  let service: IamFindUserByUsernamePasswordService;
  let repository: { find: jest.Mock };

  beforeEach(async () => {
    repository = { find: jest.fn() };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        IamFindUserByUsernamePasswordService,
        { provide: IamSequelizeUserRepository, useValue: repository },
      ],
    }).compile();

    service = module.get(IamFindUserByUsernamePasswordService);
  });

  it('returns the user when username matches, account is active, and password verifies', async () => {
    const user = buildUser();
    repository.find.mockResolvedValue(user);

    const result = await service.main(KNOWN_USERNAME, KNOWN_PASSWORD);

    expect(result).toBe(user);
    expect(repository.find).toHaveBeenCalledWith({
      queryStatement: {
        include: [
          {
            association: 'account',
            where: { username: KNOWN_USERNAME },
          },
        ],
      },
    });
  });

  it('returns null when no user matches the username (user-not-found case)', async () => {
    repository.find.mockResolvedValue(null);

    const result = await service.main('does-not-exist', KNOWN_PASSWORD);

    expect(result).toBeNull();
  });

  it('returns null when the account is inactive (collapses to same null as user-not-found)', async () => {
    repository.find.mockResolvedValue(buildUser({ isActive: false }));

    const result = await service.main(KNOWN_USERNAME, KNOWN_PASSWORD);

    expect(result).toBeNull();
  });

  it('returns null when the password does NOT match (collapses to same null as user-not-found)', async () => {
    repository.find.mockResolvedValue(buildUser());

    const result = await service.main(KNOWN_USERNAME, 'wrong-password');

    expect(result).toBeNull();
  });

  it('returns null when the stored password is empty (defensive against null/empty hash)', async () => {
    repository.find.mockResolvedValue(buildUser({ password: '' }));

    const result = await service.main(KNOWN_USERNAME, KNOWN_PASSWORD);

    expect(result).toBeNull();
  });
});
