import { describe, it, expect, vi, type Mock } from 'vitest';
import { SignupUseCase } from './SignupUseCase';
import { EmailAddress } from '../../domain/value-objects/EmailAddress';
import { UserId } from '../../domain/value-objects/UserId';
import { User } from '../../domain/entities/User';
import type { IAuthRepository } from '../../domain/repositories/IAuthRepository';
import type { IPasswordHashingService } from '../../domain/services/PasswordHashingService';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';

vi.mock('../../domain/value-objects/PasswordHash', () => ({
  PasswordHash: {
    fromHash: (hash: string) => ({ value: hash }),
  },
}));

type SignupOutput = unknown;

describe('SignupUseCase', () => {
  let authRepo: {
    findByEmail: Mock;
    save: Mock;
  };
  let passwordService: {
    hash: Mock;
  };
  let logger: Logger;
  let output: UseCaseOutputPort<SignupOutput> & { present: Mock };
  let useCase: SignupUseCase;

  beforeEach(() => {
    authRepo = {
      findByEmail: vi.fn(),
      save: vi.fn(),
    };
    passwordService = {
      hash: vi.fn(),
    };
    logger = {
      debug: vi.fn(),
      info: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
    } as unknown as Logger;
    output = {
      present: vi.fn(),
    };

    useCase = new SignupUseCase(
      authRepo as unknown as IAuthRepository,
      passwordService as unknown as IPasswordHashingService,
      logger,
      output,
    );
  });

  it('creates and saves a new user when email is free', async () => {
    const input = {
      email: 'new@example.com',
      password: 'password123',
      displayName: 'New User',
    };

    authRepo.findByEmail.mockResolvedValue(null);
    passwordService.hash.mockResolvedValue('hashed-password');

    const result = await useCase.execute(input);

    expect(authRepo.findByEmail).toHaveBeenCalledWith(EmailAddress.create(input.email));
    expect(passwordService.hash).toHaveBeenCalledWith(input.password);
    expect(authRepo.save).toHaveBeenCalled();

    expect(result.isOk()).toBe(true);
    expect(output.present).toHaveBeenCalled();
  });

  it('throws when user already exists', async () => {
    const input = {
      email: 'existing@example.com',
      password: 'password123',
      displayName: 'Existing User',
    };

    const existingUser = User.create({
      id: UserId.create(),
      displayName: 'Existing User',
      email: input.email,
    });

    authRepo.findByEmail.mockResolvedValue(existingUser);

    const result = await useCase.execute(input);

    expect(result.isErr()).toBe(true);
  });
});