import { describe, it, expect, vi, type Mock } from 'vitest';
import {
  MarkNotificationReadUseCase,
  type MarkNotificationReadCommand,
  type MarkNotificationReadResult,
} from './MarkNotificationReadUseCase';
import type { INotificationRepository } from '../../domain/repositories/INotificationRepository';
import type { Logger, UseCaseOutputPort } from '@core/shared/application';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { Notification } from '../../domain/entities/Notification';

interface NotificationRepositoryMock {
  findById: Mock;
  update: Mock;
  markAllAsReadByRecipientId: Mock;
}

interface OutputPortMock extends UseCaseOutputPort<MarkNotificationReadResult> {
  present: Mock;
}

describe('MarkNotificationReadUseCase', () => {
  let notificationRepository: NotificationRepositoryMock;
  let logger: Logger;
  let output: OutputPortMock;
  let useCase: MarkNotificationReadUseCase;

  beforeEach(() => {
    notificationRepository = {
      findById: vi.fn(),
      update: vi.fn(),
      markAllAsReadByRecipientId: vi.fn(),
    };

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

    output = {
      present: vi.fn(),
    } as unknown as OutputPortMock;

    useCase = new MarkNotificationReadUseCase(
      notificationRepository as unknown as INotificationRepository,
      output,
      logger,
    );
  });

  it('returns NOTIFICATION_NOT_FOUND when notification is not found', async () => {
    notificationRepository.findById.mockResolvedValue(null);

    const command: MarkNotificationReadCommand = {
      notificationId: 'n1',
      recipientId: 'driver-1',
    };

    const result = await useCase.execute(command);

    expect(result).toBeInstanceOf(Result);
    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr() as ApplicationErrorCode<'NOTIFICATION_NOT_FOUND', { message: string }>;
    expect(err.code).toBe('NOTIFICATION_NOT_FOUND');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('returns RECIPIENT_MISMATCH when recipientId does not match', async () => {
    const notification = Notification.create({
      id: 'n1',
      recipientId: 'driver-2',
      type: 'system_announcement',
      title: 'Test',
      body: 'Body',
      channel: 'in_app',
    });

    notificationRepository.findById.mockResolvedValue(notification);

    const command: MarkNotificationReadCommand = {
      notificationId: 'n1',
      recipientId: 'driver-1',
    };

    const result = await useCase.execute(command);

    expect(result.isErr()).toBe(true);
    const err = result.unwrapErr() as ApplicationErrorCode<'RECIPIENT_MISMATCH', { message: string }>;
    expect(err.code).toBe('RECIPIENT_MISMATCH');
    expect(output.present).not.toHaveBeenCalled();
  });

  it('marks notification as read when unread and presents result', async () => {
    const notification = Notification.create({
      id: 'n1',
      recipientId: 'driver-1',
      type: 'system_announcement',
      title: 'Test',
      body: 'Body',
      channel: 'in_app',
    });

    notificationRepository.findById.mockResolvedValue(notification);

    const command: MarkNotificationReadCommand = {
      notificationId: 'n1',
      recipientId: 'driver-1',
    };

    const result = await useCase.execute(command);

    expect(result.isOk()).toBe(true);
    expect(notificationRepository.update).toHaveBeenCalled();
    expect(output.present).toHaveBeenCalledWith({
      notificationId: 'n1',
      recipientId: 'driver-1',
      wasAlreadyRead: false,
    });
  });
});
