import { ConflictException } from '@nestjs/common';
import { Test } from '@nestjs/testing';
import { PrismaService } from '../prisma/prisma.service';
import { AuthService } from './auth.service';
import { PasswordService } from './password.service';
import { SessionService } from './session.service';

type CriarArgs = {
  data: { email: string; passwordHash: string; displayName: string };
  select: Record<string, boolean>;
};
type ContaCriada = {
  id: string;
  email: string;
  displayName: string;
  createdAt: Date;
};

describe('AuthService.register', () => {
  const create = jest.fn<Promise<ContaCriada>, [CriarArgs]>();
  const criarSessao = jest.fn();
  let service: AuthService;

  beforeEach(async () => {
    create.mockReset();
    const mod = await Test.createTestingModule({
      providers: [
        AuthService,
        PasswordService,
        { provide: PrismaService, useValue: { user: { create } } },
        { provide: SessionService, useValue: { criar: criarSessao } },
      ],
    }).compile();
    service = mod.get(AuthService);
  });

  const dto = {
    email: 'Igor@Exemplo.com ',
    password: 'senhaSegura123',
    displayName: '  Igor  ',
  };

  it('cria a conta e não devolve senha nem hash', async () => {
    create.mockResolvedValue({
      id: 'u1',
      email: 'igor@exemplo.com',
      displayName: 'Igor',
      createdAt: new Date(),
    });

    const res = await service.register({ ...dto });

    expect(Object.keys(res)).toEqual([
      'id',
      'email',
      'displayName',
      'createdAt',
    ]);
    expect(JSON.stringify(res)).not.toContain('senhaSegura123');
    expect(JSON.stringify(res)).not.toContain('scrypt');
  });

  it('normaliza e-mail para minúsculas e apara espaços', async () => {
    create.mockResolvedValue({
      id: 'u1',
      email: 'igor@exemplo.com',
      displayName: 'Igor',
      createdAt: new Date(),
    });

    await service.register({ ...dto });

    const { data } = create.mock.calls[0][0];
    expect(data.email).toBe('igor@exemplo.com');
    expect(data.displayName).toBe('Igor');
  });

  it('nunca envia a senha em texto para o banco', async () => {
    create.mockResolvedValue({
      id: 'u1',
      email: 'igor@exemplo.com',
      displayName: 'Igor',
      createdAt: new Date(),
    });

    await service.register({ ...dto });

    const { data } = create.mock.calls[0][0];
    expect(data.passwordHash).not.toContain('senhaSegura123');
    expect(data.passwordHash.startsWith('scrypt$')).toBe(true);
    expect(data).not.toHaveProperty('password');
  });

  it('não seleciona passwordHash na resposta do banco', async () => {
    create.mockResolvedValue({
      id: 'u1',
      email: 'igor@exemplo.com',
      displayName: 'Igor',
      createdAt: new Date(),
    });

    await service.register({ ...dto });

    const { select } = create.mock.calls[0][0];
    expect(select).not.toHaveProperty('passwordHash');
  });

  it('recusa e-mail duplicado apontando para o login', async () => {
    create.mockRejectedValue(
      Object.assign(new Error('dup'), { code: 'P2002' }),
    );

    await expect(service.register({ ...dto })).rejects.toBeInstanceOf(
      ConflictException,
    );
    await expect(service.register({ ...dto })).rejects.toThrow(/login/i);
  });

  it('propaga erro que não é violação de unicidade', async () => {
    create.mockRejectedValue(new Error('banco fora'));
    await expect(service.register({ ...dto })).rejects.toThrow('banco fora');
  });
});
