import type { Logger, UseCaseOutputPort } from '@core/shared/application';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { describe, expect, it, vi, type Mock } from 'vitest';
import type { ChannelPreference, NotificationPreference, TypePreference } from '../../domain/entities/NotificationPreference';
import type { INotificationPreferenceRepository } from '../../domain/repositories/INotificationPreferenceRepository';
import type { NotificationChannel, NotificationType } from '../../domain/types/NotificationTypes';
import {
  GetNotificationPreferencesQuery,
  SetDigestModeUseCase,
  UpdateChannelPreferenceUseCase,
  UpdateQuietHoursUseCase,
  UpdateTypePreferenceUseCase,
  type GetNotificationPreferencesInput,
  type GetNotificationPreferencesResult,
  type SetDigestModeCommand,
  type SetDigestModeResult,
  type UpdateChannelPreferenceCommand,
  type UpdateChannelPreferenceResult,
  type UpdateQuietHoursCommand,
  type UpdateQuietHoursResult,
  type UpdateTypePreferenceCommand,
  type UpdateTypePreferenceResult,
} from './NotificationPreferencesUseCases';

describe('NotificationPreferencesUseCases', () => {
  let preferenceRepository: {
    getOrCreateDefault: Mock;
    save: Mock;
  };
  let logger: Logger;

  
    beforeEach(() => {
      preferenceRepository = {
        getOrCreateDefault: vi.fn(),
        save: vi.fn(),
      };
  
      logger = {
        debug: vi.fn(),
        info: vi.fn(),
        warn: vi.fn(),
        error: vi.fn(),
      } as unknown as Logger;
    });
  
    it('GetNotificationPreferencesQuery returns preferences from repository', async () => {
      const preference = {
        id: 'pref-1',
      } as unknown as NotificationPreference;
  
      preferenceRepository.getOrCreateDefault.mockResolvedValue(preference);
  
      const output: UseCaseOutputPort<GetNotificationPreferencesResult> & { present: Mock } = {
        present: vi.fn(),
      } as unknown as UseCaseOutputPort<GetNotificationPreferencesResult> & { present: Mock };
  
      const useCase = new GetNotificationPreferencesQuery(
        preferenceRepository as unknown as INotificationPreferenceRepository,
        output,
        logger,
      );
  
      const input: GetNotificationPreferencesInput = { driverId: 'driver-1' };
      const result = await useCase.execute(input);
  
      expect(preferenceRepository.getOrCreateDefault).toHaveBeenCalledWith('driver-1');
      expect(result).toBeInstanceOf(Result);
      expect(result.isOk()).toBe(true);
      expect(output.present).toHaveBeenCalledWith({ preference });
    });
  it('UpdateChannelPreferenceUseCase updates channel preference', async () => {
    const preference = {
      updateChannel: vi.fn().mockReturnThis(),
    } as unknown as NotificationPreference;

    preferenceRepository.getOrCreateDefault.mockResolvedValue(preference);

    const output: UseCaseOutputPort<UpdateChannelPreferenceResult> & { present: Mock } = {
      present: vi.fn(),
    } as unknown as UseCaseOutputPort<UpdateChannelPreferenceResult> & { present: Mock };

    const useCase = new UpdateChannelPreferenceUseCase(
      preferenceRepository as unknown as INotificationPreferenceRepository,
      output,
      logger,
    );

    const command: UpdateChannelPreferenceCommand = {
      driverId: 'driver-1',
      channel: 'email' as NotificationChannel,
      preference: { enabled: true } as ChannelPreference,
    };

    const result = await useCase.execute(command);

    expect(result.isOk()).toBe(true);
    expect(preference.updateChannel).toHaveBeenCalled();
    expect(preferenceRepository.save).toHaveBeenCalledWith(preference);
    expect(output.present).toHaveBeenCalledWith({ driverId: 'driver-1', channel: 'email' });
  });

  it('UpdateTypePreferenceUseCase updates type preference', async () => {
    const preference = {
      updateTypePreference: vi.fn().mockReturnThis(),
    } as unknown as NotificationPreference;

    preferenceRepository.getOrCreateDefault.mockResolvedValue(preference);

    const output: UseCaseOutputPort<UpdateTypePreferenceResult> & { present: Mock } = {
      present: vi.fn(),
    } as unknown as UseCaseOutputPort<UpdateTypePreferenceResult> & { present: Mock };

    const useCase = new UpdateTypePreferenceUseCase(
      preferenceRepository as unknown as INotificationPreferenceRepository,
      output,
      logger,
    );

    const command: UpdateTypePreferenceCommand = {
      driverId: 'driver-1',
      type: 'system_announcement' as NotificationType,
      preference: { enabled: true } as TypePreference,
    };

    const result = await useCase.execute(command);

    expect(result.isOk()).toBe(true);
    expect(preference.updateTypePreference).toHaveBeenCalled();
    expect(preferenceRepository.save).toHaveBeenCalledWith(preference);
    expect(output.present).toHaveBeenCalledWith({ driverId: 'driver-1', type: 'system_announcement' });
  });

  it('UpdateQuietHoursUseCase validates hours and updates preferences', async () => {
    const preference = {
      updateQuietHours: vi.fn().mockReturnThis(),
    } as unknown as NotificationPreference;

    preferenceRepository.getOrCreateDefault.mockResolvedValue(preference);

    const output: UseCaseOutputPort<UpdateQuietHoursResult> & { present: Mock } = {
      present: vi.fn(),
    } as unknown as UseCaseOutputPort<UpdateQuietHoursResult> & { present: Mock };

    const useCase = new UpdateQuietHoursUseCase(
      preferenceRepository as unknown as INotificationPreferenceRepository,
      output,
      logger,
    );

    const command: UpdateQuietHoursCommand = {
      driverId: 'driver-1',
      startHour: 22,
      endHour: 7,
    };

    const result = await useCase.execute(command);

    expect(result.isOk()).toBe(true);
    expect(preference.updateQuietHours).toHaveBeenCalledWith(22, 7);
    expect(preferenceRepository.save).toHaveBeenCalledWith(preference);
    expect(output.present).toHaveBeenCalledWith({
      driverId: 'driver-1',
      startHour: 22,
      endHour: 7,
    });
  });

  it('UpdateQuietHoursUseCase returns error on invalid hours', async () => {
    const output: UseCaseOutputPort<UpdateQuietHoursResult> & { present: Mock } = {
      present: vi.fn(),
    } as unknown as UseCaseOutputPort<UpdateQuietHoursResult> & { present: Mock };

    const useCase = new UpdateQuietHoursUseCase(
      preferenceRepository as unknown as INotificationPreferenceRepository,
      output,
      logger,
    );

    const badStart: UpdateQuietHoursCommand = { driverId: 'd1', startHour: -1, endHour: 10 };
    const result1 = await useCase.execute(badStart);
    expect(result1.isErr()).toBe(true);
    const err1 = result1.unwrapErr() as ApplicationErrorCode<'INVALID_START_HOUR', { message: string }>;
    expect(err1.code).toBe('INVALID_START_HOUR');

    const badEnd: UpdateQuietHoursCommand = { driverId: 'd1', startHour: 10, endHour: 24 };
    const result2 = await useCase.execute(badEnd);
    expect(result2.isErr()).toBe(true);
    const err2 = result2.unwrapErr() as ApplicationErrorCode<'INVALID_END_HOUR', { message: string }>;
    expect(err2.code).toBe('INVALID_END_HOUR');
  });

  it('SetDigestModeUseCase sets digest mode with valid frequency', async () => {
    const preference = {
      setDigestMode: vi.fn().mockReturnThis(),
    } as unknown as NotificationPreference;

    preferenceRepository.getOrCreateDefault.mockResolvedValue(preference);

    const output: UseCaseOutputPort<SetDigestModeResult> & { present: Mock } = {
      present: vi.fn(),
    } as unknown as UseCaseOutputPort<SetDigestModeResult> & { present: Mock };

    const useCase = new SetDigestModeUseCase(
      preferenceRepository as unknown as INotificationPreferenceRepository,
      output,
    );

    const command: SetDigestModeCommand = {
      driverId: 'driver-1',
      enabled: true,
      frequencyHours: 4,
    };

    const result = await useCase.execute(command);

    expect(result.isOk()).toBe(true);
    expect(preference.setDigestMode).toHaveBeenCalledWith(true, 4);
    expect(preferenceRepository.save).toHaveBeenCalledWith(preference);
    expect(output.present).toHaveBeenCalledWith({
      driverId: 'driver-1',
      enabled: true,
      frequencyHours: 4,
    });
  });

  it('SetDigestModeUseCase returns error on invalid frequency', async () => {
    const output: UseCaseOutputPort<SetDigestModeResult> & { present: Mock } = {
      present: vi.fn(),
    } as unknown as UseCaseOutputPort<SetDigestModeResult> & { present: Mock };

    const useCase = new SetDigestModeUseCase(
      preferenceRepository as unknown as INotificationPreferenceRepository,
      output,
    );

    const command: SetDigestModeCommand = {
      driverId: 'driver-1',
      enabled: true,
      frequencyHours: 0,
    };

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