/**
 * Application Use Case: SendNotificationUseCase
 * 
 * Sends a notification to a recipient through the appropriate channels
 * based on their preferences.
 */

import type { Logger, UseCaseOutputPort } from '@core/shared/application';
import { Result } from '@core/shared/application/Result';
import type { ApplicationErrorCode } from '@core/shared/errors/ApplicationErrorCode';
import { v4 as uuid } from 'uuid';
import type { NotificationData } from '../../domain/entities/Notification';
import { Notification } from '../../domain/entities/Notification';
import type { INotificationPreferenceRepository } from '../../domain/repositories/INotificationPreferenceRepository';
import type { INotificationRepository } from '../../domain/repositories/INotificationRepository';
import type { NotificationChannel, NotificationType } from '../../domain/types/NotificationTypes';
import type { NotificationDeliveryResult, NotificationGatewayRegistry } from '../ports/NotificationGateway';

export interface SendNotificationCommand {
  recipientId: string;
  type: NotificationType;
  title: string;
  body: string;
  data?: NotificationData;
  actionUrl?: string;
  /** How urgently to display this notification (default: 'silent') */
  urgency?: 'silent' | 'toast' | 'modal';
  /** Whether this notification requires a response before dismissal */
  requiresResponse?: boolean;
  /** Action buttons for modal notifications */
  actions?: Array<{
    label: string;
    type: 'primary' | 'secondary' | 'danger';
    href?: string;
    actionId?: string;
  }>;
  /** Override channels (skip preference check) */
  forceChannels?: NotificationChannel[];
}

export interface SendNotificationResult {
  /** The created notification */
  notification: Notification;
  /** Delivery results for each channel */
  deliveryResults: NotificationDeliveryResult[];
}

export type SendNotificationErrorCode = 'REPOSITORY_ERROR';

export class SendNotificationUseCase {
  constructor(
    private readonly notificationRepository: INotificationRepository,
    private readonly preferenceRepository: INotificationPreferenceRepository,
    private readonly gatewayRegistry: NotificationGatewayRegistry,
    private readonly output: UseCaseOutputPort<SendNotificationResult>,
    private readonly logger: Logger,
  ) {
    this.logger.debug('SendNotificationUseCase initialized.');
  }

  async execute(
    command: SendNotificationCommand,
  ): Promise<Result<void, ApplicationErrorCode<SendNotificationErrorCode, { message: string }>>> {
    this.logger.debug('Executing SendNotificationUseCase', { command });
    try {
      // Get recipient's preferences
      this.logger.debug('Checking notification preferences.', { type: command.type, recipientId: command.recipientId });
      const preferences = await this.preferenceRepository.getOrCreateDefault(command.recipientId);
  
      // Check if this notification type is enabled
      if (!preferences.isTypeEnabled(command.type)) {
        // User has disabled this type - create but don't deliver
        const notification = Notification.create({
          id: uuid(),
          recipientId: command.recipientId,
          type: command.type,
          title: command.title,
          body: command.body,
          channel: 'in_app',
          status: 'dismissed', // Auto-dismiss since user doesn't want these
          ...(command.data ? { data: command.data } : {}),
          ...(command.actionUrl ? { actionUrl: command.actionUrl } : {}),
        });

        await this.notificationRepository.create(notification);

        this.output.present({
          notification,
          deliveryResults: [],
        });

        return Result.ok(undefined);
      }
  
      // Determine which channels to use
      const channels =
        command.forceChannels ?? preferences.getEnabledChannelsForType(command.type);
  
      // Check quiet hours (skip external channels during quiet hours)
      const effectiveChannels = preferences.isInQuietHours()
        ? channels.filter(ch => ch === 'in_app')
        : channels;
  
      // Ensure at least in_app is used
      if (!effectiveChannels.includes('in_app')) {
        effectiveChannels.unshift('in_app');
      }
  
      const deliveryResults: NotificationDeliveryResult[] = [];
      let primaryNotification: Notification | null = null;
  
      // Send through each channel
      for (const channel of effectiveChannels) {
        const notification = Notification.create({
          id: uuid(),
          recipientId: command.recipientId,
          type: command.type,
          title: command.title,
          body: command.body,
          channel,
          ...(command.urgency ? { urgency: command.urgency } : {}),
          ...(command.data ? { data: command.data } : {}),
          ...(command.actionUrl ? { actionUrl: command.actionUrl } : {}),
          ...(command.actions ? { actions: command.actions } : {}),
          ...(command.requiresResponse !== undefined
            ? { requiresResponse: command.requiresResponse }
            : {}),
        });
  
        // Save to repository (in_app channel) or attempt delivery (external channels)
        if (channel === 'in_app') {
          await this.notificationRepository.create(notification);
          primaryNotification = notification;
          deliveryResults.push({
            success: true,
            channel,
            attemptedAt: new Date(),
          });
        } else {
          // Attempt external delivery
          const result = await this.gatewayRegistry.send(notification);
          deliveryResults.push(result);
        }
      }
  
      this.output.present({
        notification: primaryNotification!,
        deliveryResults,
      });

      return Result.ok(undefined);
    } catch (error) {
      const err = error instanceof Error ? error : new Error(String(error));
      this.logger.error('Error sending notification', err);
      return Result.err({
        code: 'REPOSITORY_ERROR',
        details: { message: err.message },
      });
    }
  }
}