import { Injectable, Inject, Logger } from '@nestjs/common';
import { Notification, NotificationChannel, NotificationStatus } from '../../domain/entities/notification.entity';
import { NOTIFICATION_REPOSITORY, INotificationRepository } from '../../domain/repositories/notification.repository.interface';
import {
  PUSH_NOTIFICATION_PROVIDER,
  SMS_NOTIFICATION_PROVIDER,
  EMAIL_NOTIFICATION_PROVIDER,
  INotificationProvider,
} from '../../domain/ports/notification-provider.interface';
import { NotificationChannelVO } from '../../domain/value-objects/notification-channel.vo';
import { NotificationStatusVO } from '../../domain/value-objects/notification-status.vo';
import { SendNotificationDto } from '../../interface/http/dto/send-notification.dto';

@Injectable()
export class NotificationService {
  private readonly logger = new Logger('NotificationService');
  private readonly providerMap: Map<NotificationChannel, INotificationProvider>;

  constructor(
    @Inject(NOTIFICATION_REPOSITORY)
    private readonly notificationRepo: INotificationRepository,
    @Inject(PUSH_NOTIFICATION_PROVIDER)
    private readonly pushProvider: INotificationProvider,
    @Inject(SMS_NOTIFICATION_PROVIDER)
    private readonly smsProvider: INotificationProvider,
    @Inject(EMAIL_NOTIFICATION_PROVIDER)
    private readonly emailProvider: INotificationProvider,
  ) {
    this.providerMap = new Map<NotificationChannel, INotificationProvider>([
      [NotificationChannel.PUSH, this.pushProvider],
      [NotificationChannel.SMS, this.smsProvider],
      [NotificationChannel.EMAIL, this.emailProvider],
    ]);
  }

  async send(data: SendNotificationDto): Promise<Notification> {
    // Validate channel via Value Object
    const channelVO = NotificationChannelVO.create(data.channel);

    const notification = await this.notificationRepo.create({
      userId: data.userId,
      channel: channelVO.value,
      title: data.title,
      body: data.body,
      data: data.data || null,
      status: NotificationStatus.PENDING,
    });

    // Dispatch based on channel using provider abstraction
    const statusVO = NotificationStatusVO.pending();

    if (channelVO.requiresExternalDelivery) {
      const provider = this.providerMap.get(channelVO.value);
      if (provider) {
        try {
          const success = await provider.send(notification);
          if (success) {
            const newStatus = statusVO.transitionTo(NotificationStatus.SENT);
            notification.status = newStatus.value;
            notification.sentAt = new Date();
          } else {
            const newStatus = statusVO.transitionTo(NotificationStatus.FAILED);
            notification.status = newStatus.value;
          }
        } catch (error) {
          this.logger.error(`Failed to send notification ${notification.id}: ${error.message}`);
          const newStatus = statusVO.transitionTo(NotificationStatus.FAILED);
          notification.status = newStatus.value;
        }
      }
    } else {
      // IN_APP notifications are considered sent immediately (just stored)
      const newStatus = statusVO.transitionTo(NotificationStatus.SENT);
      notification.status = newStatus.value;
      notification.sentAt = new Date();
    }

    return this.notificationRepo.save(notification);
  }

  async getByUserId(userId: string, page: number, limit: number) {
    const [items, total] = await this.notificationRepo.findByUserId(
      userId,
      (page - 1) * limit,
      limit,
    );
    return { items, total, page, limit };
  }

  async markAsRead(id: string, userId: string): Promise<void> {
    const notification = await this.notificationRepo.findByIdAndUserId(id, userId);
    if (!notification) {
      return;
    }

    // Use value object to validate status transition
    const currentStatus = NotificationStatusVO.create(notification.status);
    if (currentStatus.canTransitionTo(NotificationStatus.READ)) {
      const newStatus = currentStatus.transitionTo(NotificationStatus.READ);
      await this.notificationRepo.updateStatus(id, newStatus.value, { readAt: new Date() });
    }
  }

  async countUnread(userId: string): Promise<number> {
    return this.notificationRepo.countByUserIdAndStatus(userId, NotificationStatus.SENT);
  }
}
