import type { Logger } from '@core/shared/domain/Logger';
import { Result } from '@core/shared/domain/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { beforeEach, describe, expect, it, vi, type Mock } from 'vitest';
import { Notification } from '../../domain/entities/Notification';
import { NotificationRepository } from '../../domain/repositories/NotificationRepository';
import {
    DismissNotificationUseCase,
    MarkAllNotificationsReadUseCase,
    MarkNotificationReadUseCase,
    type DismissNotificationCommand,
    type MarkAllNotificationsReadInput,
    type MarkNotificationReadCommand,
} from './MarkNotificationReadUseCase';

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

describe('MarkNotificationReadUseCase', () => {
  let notificationRepository: NotificationRepositoryMock;
  let logger: Logger;
  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;

    useCase = new MarkNotificationReadUseCase(
      notificationRepository as unknown as NotificationRepository,
      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');
  });

  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');
  });

  it('marks notification as read when unread and returns success 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();
    
    const successResult = result.unwrap();
    expect(successResult.notificationId).toBe('n1');
    expect(successResult.recipientId).toBe('driver-1');
    expect(successResult.wasAlreadyRead).toBe(false);
  });

  it('returns already read when notification is already read', async () => {
    const notification = Notification.create({
      id: 'n1',
      recipientId: 'driver-1',
      type: 'system_announcement',
      title: 'Test',
      body: 'Body',
      channel: 'in_app',
    });
    
    // Mark as read
    const readNotification = notification.markAsRead();
    notificationRepository.findById.mockResolvedValue(readNotification);

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

    const result = await useCase.execute(command);

    expect(result.isOk()).toBe(true);
    expect(notificationRepository.update).not.toHaveBeenCalled();
    
    const successResult = result.unwrap();
    expect(successResult.wasAlreadyRead).toBe(true);
  });
});

describe('MarkAllNotificationsReadUseCase', () => {
  let notificationRepository: NotificationRepositoryMock;
  let useCase: MarkAllNotificationsReadUseCase;

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

    useCase = new MarkAllNotificationsReadUseCase(
      notificationRepository as unknown as NotificationRepository,
    );
  });

  it('marks all notifications as read', async () => {
    const input: MarkAllNotificationsReadInput = {
      recipientId: 'driver-1',
    };

    const result = await useCase.execute(input);

    expect(notificationRepository.markAllAsReadByRecipientId).toHaveBeenCalledWith('driver-1');
    expect(result.isOk()).toBe(true);
    
    const successResult = result.unwrap();
    expect(successResult.recipientId).toBe('driver-1');
  });

  it('handles repository errors', async () => {
    notificationRepository.markAllAsReadByRecipientId.mockRejectedValue(new Error('DB error'));

    const input: MarkAllNotificationsReadInput = {
      recipientId: 'driver-1',
    };

    const result = await useCase.execute(input);

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

describe('DismissNotificationUseCase', () => {
  let notificationRepository: NotificationRepositoryMock;
  let useCase: DismissNotificationUseCase;

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

    useCase = new DismissNotificationUseCase(
      notificationRepository as unknown as NotificationRepository,
    );
  });

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

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

    const result = await useCase.execute(command);

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

  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: DismissNotificationCommand = {
      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');
  });

  it('dismisses notification and returns success 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: DismissNotificationCommand = {
      notificationId: 'n1',
      recipientId: 'driver-1',
    };

    const result = await useCase.execute(command);

    expect(result.isOk()).toBe(true);
    expect(notificationRepository.update).toHaveBeenCalled();
    
    const successResult = result.unwrap();
    expect(successResult.notificationId).toBe('n1');
    expect(successResult.recipientId).toBe('driver-1');
    expect(successResult.wasAlreadyDismissed).toBe(false);
  });

  it('returns already dismissed when notification is already dismissed', async () => {
    const notification = Notification.create({
      id: 'n1',
      recipientId: 'driver-1',
      type: 'system_announcement',
      title: 'Test',
      body: 'Body',
      channel: 'in_app',
    });
    
    // Dismiss it
    const dismissedNotification = notification.dismiss();
    notificationRepository.findById.mockResolvedValue(dismissedNotification);

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

    const result = await useCase.execute(command);

    expect(result.isOk()).toBe(true);
    expect(notificationRepository.update).not.toHaveBeenCalled();
    
    const successResult = result.unwrap();
    expect(successResult.wasAlreadyDismissed).toBe(true);
  });

  it('returns CANNOT_DISMISS_REQUIRING_RESPONSE when notification requires response', async () => {
    const notification = Notification.create({
      id: 'n1',
      recipientId: 'driver-1',
      type: 'system_announcement',
      title: 'Test',
      body: 'Body',
      channel: 'in_app',
      requiresResponse: true,
    });

    notificationRepository.findById.mockResolvedValue(notification);

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

    const result = await useCase.execute(command);

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