import { beforeEach, expect, test } from 'bun:test';

import { prismaMock, sendMailMock, transactionMock } from '../support/prisma.mock';

const { ComplexMembersError, inviteComplexUser } = await import(
  '@/modules/complex/services/complex-members.service'
);

beforeEach(() => {
  prismaMock._reset();
  sendMailMock.mockClear();
  transactionMock.mockClear();
});

test('creates a pending invitation and sends the email', async () => {
  prismaMock.complexUser.findUnique.mockResolvedValue({ role: 'ADMIN' } as never);
  prismaMock.complex.findUnique.mockResolvedValue({ complexName: 'Playzer Norte' } as never);
  prismaMock.user.findUnique.mockResolvedValue(null);
  prismaMock.complexInvitation.findFirst.mockResolvedValue(null);
  prismaMock.complexInvitation.create.mockResolvedValue({
    id: 'inv-1',
    complexId: 'complex-1',
    email: 'new.member@example.com',
    tokenHash: 'token-hash',
    expiresAt: new Date('2027-01-01T00:00:00.000Z'),
    acceptedAt: null,
    revokedAt: null,
    createdAt: new Date('2026-12-01T00:00:00.000Z'),
  } as never);

  const result = await inviteComplexUser('admin-1', 'complex-1', {
    email: '  New.Member@example.com ',
  });

  expect(result.invitation.email).toBe('new.member@example.com');
  expect(result.invitation.status).toBe('PENDING');
  expect(prismaMock.complexInvitation.create).toHaveBeenCalledTimes(1);

  const createArgs = prismaMock.complexInvitation.create.mock.calls[0]?.[0] as {
    data: {
      complexId: string;
      email: string;
      id: string;
      tokenHash: string;
      expiresAt: Date;
    };
  };

  expect(createArgs.data.complexId).toBe('complex-1');
  expect(createArgs.data.email).toBe('new.member@example.com');
  expect(typeof createArgs.data.id).toBe('string');
  expect(typeof createArgs.data.tokenHash).toBe('string');
  expect(sendMailMock).toHaveBeenCalledTimes(1);
  expect(transactionMock).toHaveBeenCalledTimes(1);

  const mailArgs = sendMailMock.mock.calls[0][0] as {
    to: string;
    subject: string;
  };

  expect(mailArgs.to).toBe('new.member@example.com');
  expect(mailArgs.subject).toContain('Playzer Norte');
});

test('rejects when the invite target already belongs to the complex', async () => {
  prismaMock.complexUser.findUnique
    .mockResolvedValueOnce({ role: 'ADMIN' } as never)
    .mockResolvedValueOnce({ userId: 'member-1' } as never);
  prismaMock.complex.findUnique.mockResolvedValue({ complexName: 'Playzer Norte' } as never);
  prismaMock.user.findUnique.mockResolvedValue({ id: 'member-1' } as never);

  let caught: unknown;

  try {
    await inviteComplexUser('admin-1', 'complex-1', {
      email: 'member@example.com',
    });
  } catch (error) {
    caught = error;
  }

  expect(caught).toBeInstanceOf(ComplexMembersError);
  expect((caught as InstanceType<typeof ComplexMembersError>).status).toBe(409);
  expect(transactionMock).not.toHaveBeenCalled();
  expect(sendMailMock).not.toHaveBeenCalled();
});
