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 type { ChannelPreference, NotificationPreference, TypePreference } from '../../domain/entities/NotificationPreference';
import { NotificationPreferenceRepository } from '../../domain/repositories/NotificationPreferenceRepository';
import type { NotificationChannel, NotificationType } from '../../domain/types/NotificationTypes';
import {
    GetNotificationPreferencesQuery,
    SetDigestModeUseCase,
    UpdateChannelPreferenceUseCase,
    UpdateQuietHoursUseCase,
    UpdateTypePreferenceUseCase,
    type GetNotificationPreferencesInput,
    type SetDigestModeCommand,
    type UpdateChannelPreferenceCommand,
    type UpdateQuietHoursCommand,
    type UpdateTypePreferenceCommand,
} 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 useCase = new GetNotificationPreferencesQuery(
      preferenceRepository as unknown as NotificationPreferenceRepository,
      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);
    
    const successResult = result.unwrap();
    expect(successResult.preference).toEqual(preference);
  });

  it('UpdateChannelPreferenceUseCase updates channel preference', async () => {
    const preference = {
      updateChannel: vi.fn().mockReturnThis(),
    } as unknown as NotificationPreference;

    preferenceRepository.getOrCreateDefault.mockResolvedValue(preference);

    const useCase = new UpdateChannelPreferenceUseCase(
      preferenceRepository as unknown as NotificationPreferenceRepository,
      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);
    
    const successResult = result.unwrap();
    expect(successResult.driverId).toBe('driver-1');
    expect(successResult.channel).toBe('email');
  });

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

    preferenceRepository.getOrCreateDefault.mockResolvedValue(preference);

    const useCase = new UpdateTypePreferenceUseCase(
      preferenceRepository as unknown as NotificationPreferenceRepository,
      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);
    
    const successResult = result.unwrap();
    expect(successResult.driverId).toBe('driver-1');
    expect(successResult.type).toBe('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 useCase = new UpdateQuietHoursUseCase(
      preferenceRepository as unknown as NotificationPreferenceRepository,
      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);
    
    const successResult = result.unwrap();
    expect(successResult.driverId).toBe('driver-1');
    expect(successResult.startHour).toBe(22);
    expect(successResult.endHour).toBe(7);
  });

  it('UpdateQuietHoursUseCase returns error on invalid hours', async () => {
    const useCase = new UpdateQuietHoursUseCase(
      preferenceRepository as unknown as NotificationPreferenceRepository,
      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 useCase = new SetDigestModeUseCase(
      preferenceRepository as unknown as NotificationPreferenceRepository,
    );

    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);
    
    const successResult = result.unwrap();
    expect(successResult.driverId).toBe('driver-1');
    expect(successResult.enabled).toBe(true);
    expect(successResult.frequencyHours).toBe(4);
  });

  it('SetDigestModeUseCase returns error on invalid frequency', async () => {
    const useCase = new SetDigestModeUseCase(
      preferenceRepository as unknown as NotificationPreferenceRepository,
    );

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