import { Injectable } from '@nestjs/common';
import { InjectRepository, InjectDataSource } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
import { Notification } from './entities/notification.entity';
import { NotificationConfig } from './entities/notification-config.entity';
import { NotificationGateway } from './notification.gateway';
import { SendNotificationDto } from './dto/send-notification.dto';
import { CreateConfigDto } from './dto/create-config.dto';
import { NotificationType } from './enums/notification-type.enum';
import { PushService } from './push.service';
import { MailsService } from '../mails/mails.service'; // 🆕 ADD
import { WhatsappService } from './whatsapp.service'; // 🆕 ADD

@Injectable()
export class NotificationsService {
  constructor(
    @InjectRepository(Notification, 'scheme_dbs')
    private readonly notificationRepository: Repository<Notification>,

    @InjectRepository(NotificationConfig, 'scheme_dbs')
    private readonly configRepository: Repository<NotificationConfig>,

    @InjectDataSource('scheme_dbs')
    private readonly dataSource: DataSource,

    private readonly gateway: NotificationGateway,
    private readonly pushService: PushService,
    private readonly mailsService: MailsService, // 🆕 ADD
    private readonly whatsappService: WhatsappService, // 🆕 ADD
  ) {}

  async send(dto: SendNotificationDto): Promise<Notification> {
    let roleIds = dto.target_role_ids || [];
    if (roleIds.length === 0) {
      const configs = await this.configRepository.find({
        where: {
          event_type: dto.type as unknown as NotificationType,
          is_active: true,
        },
      });
      roleIds = configs.map((c) => c.role_id);
    }

    console.log('🎯 Target roleIds:', roleIds);
    console.log('🔔 Notification type:', dto.type);

    const notification = this.notificationRepository.create({
      ...dto,
      target_role_ids: roleIds,
    });
    const saved = await this.notificationRepository.save(notification);

    console.log('💾 Saved notification id:', saved.id);

    const event = dto.is_urgent
      ? 'notification:urgent'
      : dto.requires_action
        ? 'notification:action'
        : 'notification';

    console.log('📡 Emitting event:', event, 'to roles:', roleIds);

    this.gateway.emitToRoles(roleIds, event, saved);

    if (dto.target_user_ids?.length) {
      this.gateway.emitToUsers(dto.target_user_ids, event, saved);
    }

    const pushPayload = {
      title: saved.title,
      body: saved.body,
      is_urgent: saved.is_urgent,
    };

    // ─── Web push (existing) ────────────────────────────────────────
    let resolvedUserIds: number[] = [];
    if (dto.target_user_ids?.length) {
      resolvedUserIds = dto.target_user_ids;
      await this.pushService.sendToUsers(resolvedUserIds, pushPayload);
    } else if (roleIds.length) {
      const placeholders = roleIds.map(() => '?').join(',');
      const rows = await this.dataSource.query(
        `SELECT DISTINCT user_id FROM user_roles WHERE role_id IN (${placeholders})`,
        roleIds,
      );
      resolvedUserIds = rows.map((r: any) => Number(r.user_id));
      console.log('📲 Sending push to userIds:', resolvedUserIds);
      await this.pushService.sendToUsers(resolvedUserIds, pushPayload);
    }

    // ═══════════════════════════════════════════════════════════════
    // 🆕 NEW — Email + WhatsApp dispatch (if requested in DTO)
    // ═══════════════════════════════════════════════════════════════
    if (
      (dto.email_subject && dto.email_html) ||
      dto.whatsapp_message
    ) {
      await this.dispatchExtraChannels(dto, resolvedUserIds);
    }

    return saved;
  }

  /**
   * 🆕 Fetches recipient user records (email + phone) and dispatches
   * email + WhatsApp in parallel. Never throws — failures are logged but
   * don't break the main notification flow.
   */
  private async dispatchExtraChannels(
    dto: SendNotificationDto,
    userIds: number[],
  ): Promise<void> {
    if (!userIds.length) return;

    // Fetch user records once
    const placeholders = userIds.map(() => '?').join(',');
    const users: Array<{
      id: number;
      email: string;
      firstName: string;
      lastName: string;
      phone_number: string | null;
    }> = await this.dataSource.query(
      `SELECT id, email, firstName, lastName, phone_number
         FROM users
        WHERE id IN (${placeholders})`,
      userIds,
    );

    const tasks: Promise<any>[] = [];

    // Email
    if (dto.email_subject && dto.email_html) {
      for (const user of users) {
        if (!user.email) continue;
        tasks.push(
          this.mailsService
            .sendCustom(user.email, dto.email_subject, dto.email_html)
            .catch((err) =>
              console.warn(
                `[Notifications] Email failed for ${user.email}: ${err.message}`,
              ),
            ),
        );
      }
    }

    // WhatsApp
    if (dto.whatsapp_message) {
      for (const user of users) {
        if (!user.phone_number) continue;
        tasks.push(
          this.whatsappService
            .sendText(user.phone_number, dto.whatsapp_message)
            .catch((err) =>
              console.warn(
                `[Notifications] WhatsApp failed for ${user.phone_number}: ${err.message}`,
              ),
            ),
        );
      }
    }

    await Promise.allSettled(tasks);
  }

  // ── REST endpoints ────────────────────────────────

  /**
   * Send a one-time-password to a client by email. Called by
   * ClientAuthService during login. Uses the same MailsService the rest
   * of the app uses (sendCustom → default mailbox). Accepts an optional
   * name for a friendlier greeting.
   *
   * Restored: ClientAuthService checks `typeof sendEmailOtp === 'function'`
   * and only sends if it exists; without this method it logged
   * "sendEmailOtp not implemented" and no OTP email went out.
   */
  async sendEmailOtp(email: string, otp: string | number, name?: string): Promise<void> {
    const code = String(otp);
    const greeting = name ? `Hello ${name},` : 'Hello,';
    const subject = `Your QRS verification code: ${code}`;
    const html = `
      <div style="font-family:Arial,Helvetica,sans-serif;max-width:520px;margin:0 auto;padding:24px;color:#1e293b">
        <div style="text-align:center;margin-bottom:20px">
          <div style="font-size:20px;font-weight:800;color:#4a0080;letter-spacing:.5px">QUALITY REGISTRAR SYSTEMS</div>
          <div style="font-size:12px;color:#64748b">Client Portal Login</div>
        </div>
        <p style="font-size:14px">${greeting}</p>
        <p style="font-size:14px">Use the verification code below to sign in to your QRS client portal. This code expires shortly.</p>
        <div style="margin:22px 0;text-align:center">
          <div style="display:inline-block;background:#f5f3ff;border:1px solid #e9d5ff;border-radius:12px;
                      padding:16px 30px;font-size:30px;font-weight:800;letter-spacing:8px;color:#4a0080">${code}</div>
        </div>
        <p style="font-size:12px;color:#64748b">If you did not request this code, you can safely ignore this email.</p>
        <hr style="border:none;border-top:1px solid #ede9fe;margin:20px 0"/>
        <p style="font-size:11px;color:#94a3b8;text-align:center">Quality Registrar Systems · CertifyHub</p>
      </div>`;
    try {
      await this.mailsService.sendCustom(email, subject, html);
      console.log(`✓ OTP email sent to ${email}`);
    } catch (err: any) {
      console.error(`✗ OTP email to ${email} failed: ${err.message}`);
      throw err;
    }
  }

  async findAll(userId: number, unreadOnly = false): Promise<Notification[]> {
    // A user should see a notification when EITHER they are a direct
    // recipient (their id in target_user_ids) OR one of their roles is in
    // target_role_ids. These are simple-array columns stored as comma-joined
    // strings ("41" or "41,42"), so FIND_IN_SET matches an id inside them.
    // The old query required a role, which hid user-only notifications
    // (e.g. checklist notifications, which target a client by user id and
    // carry no role).
    const roleIds = await this.getUserRoleIds(userId);

    const qb = this.notificationRepository
      .createQueryBuilder('n')
      .orderBy('n.created_at', 'DESC')
      .take(50);

    if (roleIds.length) {
      const roleOr = roleIds
        .map((_, i) => `FIND_IN_SET(:r${i}, n.target_role_ids)`)
        .join(' OR ');
      const params: Record<string, string> = { uid: String(userId) };
      roleIds.forEach((r, i) => (params[`r${i}`] = String(r)));
      qb.where(
        `(FIND_IN_SET(:uid, n.target_user_ids) OR ${roleOr})`,
        params,
      );
    } else {
      qb.where('FIND_IN_SET(:uid, n.target_user_ids)', { uid: String(userId) });
    }

    if (unreadOnly) {
      qb.andWhere('n.is_read = :isRead', { isRead: false });
    }

    return qb.getMany();
  }

  /** Role ids for a user, from the user_roles bridge. Empty for pure
   *  client-portal users (they are targeted by user id, not role). */
  private async getUserRoleIds(userId: number): Promise<number[]> {
    if (!userId) return [];
    try {
      const rows: any[] = await this.dataSource.query(
        'SELECT role_id FROM user_roles WHERE user_id = ?',
        [userId],
      );
      return rows.map((r) => Number(r.role_id)).filter((n) => Number.isFinite(n));
    } catch {
      return [];
    }
  }

  async getUnreadCount(userId: number): Promise<number> {
    // Count only THIS user's unread notifications (direct or via a role).
    // The old version counted all unread rows for everyone and ignored
    // userId entirely.
    const roleIds = await this.getUserRoleIds(userId);

    const qb = this.notificationRepository
      .createQueryBuilder('n')
      .where('n.is_read = :isRead', { isRead: false });

    if (roleIds.length) {
      const roleOr = roleIds
        .map((_, i) => `FIND_IN_SET(:r${i}, n.target_role_ids)`)
        .join(' OR ');
      const params: Record<string, string> = { uid: String(userId) };
      roleIds.forEach((r, i) => (params[`r${i}`] = String(r)));
      qb.andWhere(
        `(FIND_IN_SET(:uid, n.target_user_ids) OR ${roleOr})`,
        params,
      );
    } else {
      qb.andWhere('FIND_IN_SET(:uid, n.target_user_ids)', { uid: String(userId) });
    }

    return qb.getCount();
  }

  async markRead(id: number, userId: number): Promise<void> {
    await this.notificationRepository.update(id, {
      is_read: true,
      read_by_user_id: userId,
    });
  }

  async markAllRead(userId: number): Promise<void> {
    // Only mark THIS user's notifications read (direct or via a role).
    const roleIds = await this.getUserRoleIds(userId);
    const qb = this.notificationRepository
      .createQueryBuilder()
      .update(Notification)
      .set({ is_read: true, read_by_user_id: userId })
      .where('is_read = false');

    if (roleIds.length) {
      const roleOr = roleIds
        .map((_, i) => `FIND_IN_SET(:r${i}, target_role_ids)`)
        .join(' OR ');
      const params: Record<string, string> = { uid: String(userId) };
      roleIds.forEach((r, i) => (params[`r${i}`] = String(r)));
      qb.andWhere(
        `(FIND_IN_SET(:uid, target_user_ids) OR ${roleOr})`,
        params,
      );
    } else {
      qb.andWhere('FIND_IN_SET(:uid, target_user_ids)', { uid: String(userId) });
    }

    await qb.execute();
  }

  // ── Config endpoints ──────────────────────────────

  async createConfig(data: CreateConfigDto): Promise<NotificationConfig> {
    const config = this.configRepository.create({
      event_type: data.event_type as NotificationType,
      role_id: data.role_id,
      description: data.description,
      is_active: data.is_active ?? true,
    });
    return this.configRepository.save(config);
  }

  async findAllConfigs(): Promise<NotificationConfig[]> {
    return this.configRepository.find({
      order: { event_type: 'ASC' },
    });
  }

  async updateConfig(
    id: number,
    data: { is_active?: boolean; description?: string },
  ): Promise<void> {
    await this.configRepository.update(id, data);
  }

  async deleteConfig(id: number): Promise<void> {
    await this.configRepository.delete(id);
  }
}