import type { Logger } from '@core/shared/domain/Logger';
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
import { LoginWithEmailUseCase } from './LoginWithEmailUseCase';

// Mock the PasswordHash module
vi.mock('@core/identity/domain/value-objects/PasswordHash', () => ({
  PasswordHash: {
    fromHash: vi.fn((hash: string) => ({
      verify: vi.fn().mockResolvedValue(hash === 'hashed-password'),
      value: hash,
    })),
  },
}));

describe('LoginWithEmailUseCase', () => {
  let userRepository: {
    findByEmail: Mock;
  };
  let sessionPort: {
    createSession: Mock;
  };
  let logger: Logger;
  let useCase: LoginWithEmailUseCase;

  beforeEach(() => {
    userRepository = {
      findByEmail: vi.fn(),
    };

    sessionPort = {
      createSession: vi.fn(),
    };

    logger = {
      debug: vi.fn(),
      info: vi.fn(),
      warn: vi.fn(),
      error: vi.fn(),
    } as unknown as Logger;

    useCase = new LoginWithEmailUseCase(
      userRepository as unknown as UserRepository,
      sessionPort as unknown as IdentitySessionPort,
      logger,
    );
  });

  it('returns ok and presents session result for valid credentials', async () => {
    const storedUser = {
      id: 'user-1',
      email: 'test@example.com',
      displayName: 'John Smith',
      passwordHash: 'hashed-password',
      createdAt: new Date(),
    };
    userRepository.findByEmail.mockResolvedValue(storedUser);
    sessionPort.createSession.mockResolvedValue({
      token: 'token-123',
      user: {
        id: 'user-1',
        email: 'test@example.com',
        displayName: 'John Smith',
      },
      issuedAt: Date.now(),
      expiresAt: Date.now() + 1000,
    });

    const result = await useCase.execute({
      email: 'test@example.com',
      password: 'Password123',
    });

    expect(result.isOk()).toBe(true);
    const loginResult = result.unwrap();
    expect(loginResult.sessionToken).toBe('token-123');
    expect(loginResult.userId).toBe('user-1');
    expect(loginResult.displayName).toBe('John Smith');
    expect(loginResult.email).toBe('test@example.com');
    expect(userRepository.findByEmail).toHaveBeenCalledWith('test@example.com');
    expect(sessionPort.createSession).toHaveBeenCalled();
  });

  it('returns INVALID_INPUT when email or password is missing', async () => {
    const result = await useCase.execute({
      email: '',
      password: 'Password123',
    });

    expect(result.isErr()).toBe(true);
    expect(result.unwrapErr().code).toBe('INVALID_INPUT');
  });

  it('returns INVALID_CREDENTIALS when user does not exist', async () => {
    userRepository.findByEmail.mockResolvedValue(null);

    const result = await useCase.execute({
      email: 'nonexistent@example.com',
      password: 'Password123',
    });

    expect(result.isErr()).toBe(true);
    expect(result.unwrapErr().code).toBe('INVALID_CREDENTIALS');
  });

  it('returns INVALID_CREDENTIALS when password is invalid', async () => {
    const storedUser = {
      id: 'user-1',
      email: 'test@example.com',
      displayName: 'John Smith',
      passwordHash: 'wrong-hash', // Different hash to simulate wrong password
      createdAt: new Date(),
    };
    userRepository.findByEmail.mockResolvedValue(storedUser);

    const result = await useCase.execute({
      email: 'test@example.com',
      password: 'WrongPassword',
    });

    expect(result.isErr()).toBe(true);
    expect(result.unwrapErr().code).toBe('INVALID_CREDENTIALS');
  });

  it('wraps unexpected errors as REPOSITORY_ERROR and logs them', async () => {
    userRepository.findByEmail.mockRejectedValue(new Error('Database connection failed'));

    const result = await useCase.execute({
      email: 'test@example.com',
      password: 'Password123',
    });

    expect(result.isErr()).toBe(true);
    expect(result.unwrapErr().code).toBe('REPOSITORY_ERROR');
    expect(logger.error).toHaveBeenCalled();
  });
});