import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
import { DomainException, EventBusService, LoggerService } from '@modules/shared';
import { NotificationSentEvent } from '../../../domain/events/notification-sent.event';
import {
  NOTIFICATION_PREFERENCE_REPOSITORY,
  type INotificationPreferenceRepository,
} from '../../../domain/repositories/notification-preference.repository';
import {
  NOTIFICATION_REPOSITORY,
  type INotificationRepository,
} from '../../../domain/repositories/notification.repository';
import { EmailService } from '../../../infrastructure/services/email.service';
import { FcmService } from '../../../infrastructure/services/fcm.service';
import { StringeeSmsService } from '../../../infrastructure/services/stringee-sms.service';
import { TemplateService } from '../../../infrastructure/services/template.service';
import { ZaloOaService } from '../../../infrastructure/services/zalo-oa.service';
import { getZaloZnsTemplates } from '../../../infrastructure/services/zalo-zns-templates';
import { SendNotificationCommand } from './send-notification.command';

@CommandHandler(SendNotificationCommand)
export class SendNotificationHandler implements ICommandHandler<SendNotificationCommand> {
  constructor(
    @Inject(NOTIFICATION_REPOSITORY)
    private readonly notificationRepo: INotificationRepository,
    @Inject(NOTIFICATION_PREFERENCE_REPOSITORY)
    private readonly preferenceRepo: INotificationPreferenceRepository,
    private readonly emailService: EmailService,
    private readonly fcmService: FcmService,
    private readonly stringeeSmsService: StringeeSmsService,
    private readonly zaloOaService: ZaloOaService,
    private readonly templateService: TemplateService,
    private readonly eventBus: EventBusService,
    private readonly logger: LoggerService,
  ) {}

  async execute(command: SendNotificationCommand): Promise<void> {
    try {
      const { userId, channel, templateKey, templateData, recipientAddress } = command;

      // Check user preference
      const isEnabled = await this.preferenceRepo.isEnabled(userId, channel, templateKey);
      if (!isEnabled) {
        this.logger.log(
          `Notification skipped: user ${userId} disabled ${channel}/${templateKey}`,
          'SendNotificationHandler',
        );
        return;
      }

      // Render template
      const rendered = this.templateService.render(templateKey, templateData);

      // Persist notification log
      const notification = await this.notificationRepo.create({
        userId,
        channel,
        templateKey,
        subject: rendered.subject,
        body: rendered.body,
        metadata: templateData,
      });

      try {
        switch (channel) {
          case 'EMAIL':
            await this.emailService.send({
              to: recipientAddress,
              subject: rendered.subject,
              html: rendered.body,
            });
            break;

          case 'PUSH':
            await this.fcmService.send({
              token: recipientAddress,
              title: rendered.subject,
              body: rendered.body.replace(/<[^>]*>/g, ''), // Strip HTML for push
            });
            break;

          case 'SMS':
            if (!this.stringeeSmsService.isAvailable) {
              this.logger.warn(
                'SMS channel requested but Stringee is not configured — notification logged as PENDING',
                'SendNotificationHandler',
              );
              await this.notificationRepo.updateStatus(notification.id, 'PENDING');
              return;
            }
            await this.stringeeSmsService.sendNotification({
              to: recipientAddress,
              message: rendered.body.replace(/<[^>]*>/g, ''), // Strip HTML for SMS
            });
            break;

          case 'ZALO_OA': {
            if (!this.zaloOaService.isAvailable) {
              this.logger.warn(
                'ZALO_OA channel requested but Zalo OA is not configured — notification logged as PENDING',
                'SendNotificationHandler',
              );
              await this.notificationRepo.updateStatus(notification.id, 'PENDING');
              return;
            }

            const znsTemplates = getZaloZnsTemplates();
            const znsTpl = znsTemplates[templateKey];

            if (!znsTpl) {
              this.logger.warn(
                `No ZNS template mapped for "${templateKey}" — Zalo OA notification logged as PENDING`,
                'SendNotificationHandler',
              );
              await this.notificationRepo.updateStatus(notification.id, 'PENDING');
              return;
            }

            await this.zaloOaService.sendMessage({
              toUid: recipientAddress,
              templateId: znsTpl.templateId,
              templateData: znsTpl.mapParams(templateData),
            });
            break;
          }
        }

        await this.notificationRepo.updateStatus(notification.id, 'SENT');
        this.eventBus.publish(new NotificationSentEvent(notification.id, userId, channel, templateKey));
      } catch (error) {
        const errorMsg = error instanceof Error ? error.message : String(error);
        await this.notificationRepo.updateStatus(notification.id, 'FAILED', errorMsg);
        this.logger.error(
          `Notification ${notification.id} failed on ${channel}: ${errorMsg}`,
          'SendNotificationHandler',
        );
      }
    } catch (error) {
      if (error instanceof DomainException) throw error;
      this.logger.error(
        `Failed to send notification: ${error instanceof Error ? error.message : error}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );
      throw new InternalServerErrorException('Không thể gửi thông báo');
    }
  }
}
