// src/server/features/notes/lib/notifier.service.ts
// Notification orchestration service for notes

import { notify, notifyMultipleUsers, getNotifiedUsersForTarget } from '@features/notifications/notifications.service';
import { getEmployees } from '@features/personnel/personnel.service';
import { loggerService } from '@serverShared/lib/logger';
import type { UserInfo } from '@serverShared/lib/auth';
import { EVENTS } from '@shared/contracts/events';
import { USER_ROLES } from '@shared/constants/roles';
import { truncateTitle, truncateComment, removeMentionsFromContent, getStatusLabel, getNotificationMessage } from '@shared/utils/formatters';

const STAFF_NOTIFICATION_ROLES = [USER_ROLES.ADMIN, USER_ROLES.MANAGER, USER_ROLES.GOD] as const;

// Initialize logger
const notesLogger = loggerService.get('HTTP').child({ module: 'notifier' });
const logger = loggerService.get('DB').child({ module: 'notifier' });

/**
 * Отправка уведомлений упомянутым пользователям
 * Icon: MessageSquare (💬)
 * Format: {Title (max20)}: '{Message text without @}'
 * Flag: isPersonalMention: true
 *
 * Принимает массив UUID вместо имен для надежного определения пользователей
 */
async function notifyMentionedUsers(
  mentionedIds: string[],
  noteId: string,
  authorName: string,
  performerName: string,
  noteTitle?: string,
  contentText?: string,
  lastCommentAt?: string,
  authorId?: string
): Promise<void> {
  if (mentionedIds.length === 0) {
    return;
  }

  try {
    notesLogger.debug('[MENTIONS] notifyMentionedUsers called:');
    notesLogger.debug('[MENTIONS]   mentionedIds:', mentionedIds);
    notesLogger.debug('[MENTIONS]   noteId:', noteId);
    notesLogger.debug('[MENTIONS]   authorName (note author):', authorName);
    notesLogger.debug('[MENTIONS]   performerName (who wrote comment/mention):', performerName);
    notesLogger.debug('[MENTIONS]   noteTitle:', noteTitle);
    notesLogger.debug('[MENTIONS]   lastCommentAt:', lastCommentAt);
    notesLogger.debug('[MENTIONS]   authorId:', authorId);

    const employees = await getEmployees('SYSTEM');

    for (const userId of mentionedIds) {
      const employee = employees.find(emp => emp.id === userId);

      if (employee) {
        notesLogger.debug('[MENTIONS]   Found employee:', employee.fullName, '(ID:', employee.id, ')');
        notesLogger.debug('[MENTIONS]   Sending mention notification to:', employee.fullName);

        notesLogger.debug('[MENTIONS]   Calling notify() with:');
        notesLogger.debug('[MENTIONS]     authorName:', authorName);
        notesLogger.debug('[MENTIONS]     performerName:', performerName);

        // Prepare data WITHOUT message - will be generated by getNotificationMessage
        const notificationData: any = {
          noteId,
          mention: employee.fullName,
          authorName,
          performerName,
          performerRole: employee.role,
          noteTitle: truncateTitle(noteTitle || ''),
          isPersonalMention: true,
          // Critical for Socket Bridge: lastCommentAt and authorId for patchNoteDates
          lastCommentAt: lastCommentAt || new Date().toISOString(),
          authorId: authorId,
          link: {
            path: '/dashboard/notes',
            query: { noteId },
          },
        };

        // Generate message using shared formatter
        notificationData.message = getNotificationMessage(EVENTS.MENTION_NOTIFICATION, notificationData);

        await notify({
          userId: employee.id,
          event: EVENTS.MENTION_NOTIFICATION,
          data: notificationData,
          targetId: noteId,
        });

        notesLogger.debug('[MENTIONS]   Mention notification sent to', employee.fullName, '(ID:', employee.id, ')');
        logger.info(`Mention notification sent to ${employee.fullName} (id: ${employee.id}) for note ${noteId}`);
      } else {
        notesLogger.debug('[MENTIONS]   Employee not found for mention ID:', userId);
      }
    }
  } catch (error) {
    logger.error('Error sending mention notifications:', error);
  }
}

/**
 * Обработка уведомлений при создании заметки
 * Icon: StickyNote (📝)
 * Format: {Title (max20)} (👤 {Author})
 * Address: All MANAGERs and ADMINs except author
 */
export async function onNoteCreated(
  note: any,
  authorName: string,
  content: string
): Promise<void> {
  try {
    // Get all employees for role-based notifications
    const employees = await getEmployees('SYSTEM');
    const author = employees.find(emp => emp.id === note.authorId);

    // Rule: Private notes - ignore mentions and notifications
    if (!note.isPublic) {
      return;
    }

    // Rule: Notify all MANAGERs and ADMINs except author
    const targetIds = employees
      .filter(emp =>
        emp.role !== null &&
        STAFF_NOTIFICATION_ROLES.includes(emp.role as any) &&
        !emp.isFired &&
        emp.id !== note.authorId
      )
      .map(emp => emp.id);

    if (targetIds.length > 0) {
      // Prepare data with message BEFORE calling notifyMultipleUsers
      const notificationData = {
        ...note,
        authorName,
        noteTitle: truncateTitle(note.title),
        isHighPriority: note.priority === 'high',
        link: {
          path: '/dashboard/notes',
          query: { noteId: note.id },
        },
      };
      
      // Get message from formatters
      notificationData.message = getNotificationMessage('note:created', notificationData);
      
      await notifyMultipleUsers({
        userIds: targetIds,
        event: 'note:created',
        data: notificationData,
        targetId: note.id,
      });
    }

    // Parse mentions and send notifications (only for public notes)
    const { parseMentions } = await import('./parser.service');
    const mentionedIds = await parseMentions(content);
    if (mentionedIds.length > 0) {
      // Pass content as contentText for mentions (authorName is both author and performer for new notes)
      await notifyMentionedUsers(mentionedIds, note.id, authorName, authorName, note.title, content, undefined, note.authorId);
    }
  } catch (error) {
    logger.error('Error in onNoteCreated:', error);
  }
}

/**
 * Обработка уведомлений при изменении статуса заметки
 */
export async function onNoteStatusChanged(
  note: any,
  oldStatus: string,
  user: UserInfo
): Promise<void> {
  try {
    // Only notify for public notes
    if (!note.isPublic) {
      return;
    }

    const employees = await getEmployees('SYSTEM');
    
    // Get user's full name from employees list
    const editor = employees.find(emp => emp.id === user.id);
    const editorName = editor ? editor.fullName : 'Неизвестный';

    // Rule: Notify all ADMIN and MANAGER when status of a public note changes
    // Rule: Author exclusion - exclude the user who changed the status
    const targetIds = employees
      .filter(emp =>
        emp.role !== null &&
        STAFF_NOTIFICATION_ROLES.includes(emp.role as any) &&
        !emp.isFired &&
        emp.id !== user.id
      )
      .map(emp => emp.id);

    if (targetIds.length > 0) {
      // Prepare data with message BEFORE calling notifyMultipleUsers
      const notificationData = {
        ...note,
        authorName: editorName,
        noteTitle: truncateTitle(note.title),
        oldStatus,
        newStatus: note.status,
        statusLabel: getStatusLabel(note.status),
      };
      
      // Get message from formatters
      notificationData.message = getNotificationMessage('note:status_changed', notificationData);
      
      await notifyMultipleUsers({
        userIds: targetIds,
        event: 'note:status_changed',
        data: notificationData,
        targetId: note.id,
      });
    }
  } catch (error) {
    logger.error('Error in onNoteStatusChanged:', error);
  }
}

/**
 * Обработка уведомлений при изменении приоритета заметки
 * Icon: StickyNote (📝)
 * Format: {User} изменил приоритет заметки {title} на {высокий/обычный}
 * Address: All ADMINs and MANAGERs except the user who changed the priority
 * Scope: Only for public notes
 */
export async function onNotePriorityChanged(
  note: any,
  oldPriority: string,
  user: UserInfo
): Promise<void> {
  try {
    // Only notify for public notes
    if (!note.isPublic) {
      return;
    }

    const employees = await getEmployees('SYSTEM');
    
    // Get user's full name from employees list
    const editor = employees.find(emp => emp.id === user.id);
    const editorName = editor ? editor.fullName : 'Неизвестный';

    // Rule: Notify all ADMIN and MANAGER when priority of a public note changes
    // Rule: Author exclusion - exclude the user who changed the priority
    const targetIds = employees
      .filter(emp =>
        emp.role !== null &&
        STAFF_NOTIFICATION_ROLES.includes(emp.role as any) &&
        !emp.isFired &&
        emp.id !== user.id
      )
      .map(emp => emp.id);

    if (targetIds.length > 0) {
      // Determine priority label
      const priorityLabel = note.priority === 'high' ? 'высокий' : 'обычный';
      
      // Prepare data with message BEFORE calling notifyMultipleUsers
      const notificationData: any = {
        noteId: note.id,
        noteTitle: truncateTitle(note.title),
        authorName: editorName,
        oldPriority,
        newPriority: note.priority,
        link: {
          path: '/dashboard/notes',
          query: { noteId: note.id },
        },
      };
      
      // Generate message: "{User} изменил приоритет заметки {title} на {высокий/обычный}"
      notificationData.message = `${editorName} изменил приоритет заметки '${truncateTitle(note.title)}' на ${priorityLabel}`;
      
      await notifyMultipleUsers({
        userIds: targetIds,
        event: EVENTS.NOTE_PRIORITY_CHANGED,
        data: notificationData,
        targetId: note.id,
      });
      
      logger.info(`Priority change notification sent to ${targetIds.length} users for note ${note.id} by ${editorName}`);
    }
  } catch (error) {
    logger.error('Error in onNotePriorityChanged:', error);
  }
}

/**
 * Обработка уведомлений при удалении заметки
 */
export async function onNoteDeleted(noteId: string): Promise<void> {
  try {
    const employees = await getEmployees('SYSTEM');
    const adminIds = employees
      .filter(emp => emp.role === 'ADMIN' && !emp.isFired)
      .map(emp => emp.id);

    // Commented out to prevent spam notifications on note deletion
    // if (adminIds.length > 0) {
    //   await notifyMultipleUsers({
    //     userIds: adminIds,
    //     event: 'note:deleted',
    //     data: { id: noteId },
    //     targetId: noteId,
    //   });
    // }
  } catch (error) {
    logger.error('Error in onNoteDeleted:', error);
  }
}

/**
 * Обработка уведомлений при публикации заметки (private → public)
 * Icon: StickyNote (📝)
 * Format: {Title (max20)} (👤 {Author})
 * Address: All MANAGERs and ADMINs except author
 */
export async function onNotePublished(
  note: any,
  user: UserInfo
): Promise<void> {
  try {
    const employees = await getEmployees('SYSTEM');
    
    // Get user's full name from employees list
    const editor = employees.find(emp => emp.id === user.id);
    const editorName = editor ? editor.fullName : 'Неизвестный';
    
    // Rule: Publication - same logic as creating new public note
    // Notify all MANAGERs and ADMINs except author
    const targetIds = employees
      .filter(emp =>
        emp.role !== null &&
        STAFF_NOTIFICATION_ROLES.includes(emp.role as any) &&
        !emp.isFired &&
        emp.id !== user.id
      )
      .map(emp => emp.id);

    if (targetIds.length > 0) {
      // Prepare data with message BEFORE calling notifyMultipleUsers
      const notificationData = {
        ...note,
        authorName: editorName,
        noteTitle: truncateTitle(note.title),
        isHighPriority: note.priority === 'high',
        link: {
          path: '/dashboard/notes',
          query: { noteId: note.id },
        },
      };
      
      // Get message from formatters
      notificationData.message = getNotificationMessage('note:created', notificationData);
      
      await notifyMultipleUsers({
        userIds: targetIds,
        event: 'note:created',
        data: notificationData,
        targetId: note.id,
      });
    }

    // Parse mentions and send notifications
    const { parseMentions } = await import('./parser.service');
    const mentionedIds = await parseMentions(note.content);
    if (mentionedIds.length > 0) {
      // editorName is both author and performer for published notes
      await notifyMentionedUsers(mentionedIds, note.id, editorName, editorName, note.title, note.content, undefined, note.authorId);
    }
  } catch (error) {
    logger.error('Error in onNotePublished:', error);
  }
}

/**
 * Обработка уведомлений при создании комментария
 * Icon: MessageSquare (💬)
 * Format: {comment icon} {note title}: {comment text} ({icon of author}{author name})
 * Address: All ADMINs and MANAGERs (except author and personally mentioned)
 * Scope: Only for public notes
 * LOGIC:
 *   - Mentioned users receive personal mention notification
 *   - All other ADMINs and MANAGERs receive general comment notification
 *   - Strict Admin Mention Privacy: If admin is mentioned, managers don't get general notification
 */
export async function onCommentCreated(
  noteId: string,
  authorId: string,
  authorName: string,
  noteTitle: string,
  isPublic: boolean,
  contentText?: string,
  performerName?: string,
  lastCommentAt?: string
): Promise<void> {
  try {
    notesLogger.debug('[NOTIFIER] onCommentCreated called:');
    notesLogger.debug('[NOTIFIER]   noteId:', noteId);
    notesLogger.debug('[NOTIFIER]   authorId (who wrote comment):', authorId);
    notesLogger.debug('[NOTIFIER]   authorName:', authorName);
    notesLogger.debug('[NOTIFIER]   performerName:', performerName);
    notesLogger.debug('[NOTIFIER]   noteTitle:', noteTitle);
    notesLogger.debug('[NOTIFIER]   isPublic:', isPublic);
    notesLogger.debug('[NOTIFIER]   contentText:', contentText);
    notesLogger.debug('[NOTIFIER]   lastCommentAt:', lastCommentAt);
    notesLogger.debug('[NOTIFIER]   lastCommentAt type:', typeof lastCommentAt);
    
    // Rule: Only for public notes
    if (!isPublic) {
      notesLogger.debug('[NOTIFIER]   Note is private, skipping notifications');
      return;
    }

    notesLogger.debug('[NOTIFIER]   Loading employees...');
    const employees = await getEmployees('SYSTEM');
    notesLogger.debug('[NOTIFIER]   Loaded', employees.length, 'employees');
    
    // Get the author to check their role
    const author = employees.find(emp => emp.id === authorId);
    if (!author) {
      notesLogger.debug('[NOTIFIER]   Author not found for comment on note', noteId);
      logger.warn(`Author not found for comment on note ${noteId}`);
      return;
    }
    notesLogger.debug('[NOTIFIER]   Author found:', author.fullName, 'role:', author.role);

    // 1. Parse mentions in the comment (returns UUIDs directly)
    notesLogger.debug('[NOTIFIER]   Parsing mentions in comment...');
    const { parseMentions } = await import('./parser.service');
    const mentionedIds = await parseMentions(contentText || '');
    notesLogger.debug('[NOTIFIER]   Found', mentionedIds.length, 'mentioned IDs:', mentionedIds);

    // 2. Get existing mention notifications for this comment from DB
    // (to exclude users who already received a mention notification)
    notesLogger.debug('[NOTIFIER]   Getting already notified users...');
    const alreadyNotifiedUserIds = await getNotifiedUsersForTarget(noteId, EVENTS.MENTION_NOTIFICATION);
    notesLogger.debug('[NOTIFIER]   Already notified users count:', alreadyNotifiedUserIds.size);

    // 3. Send mention notifications to ALL mentioned users
    // (including admins - they receive personal mention notifications)
    // Priority: Personal mentions always delivered, even if user is also admin/manager
    if (mentionedIds.length > 0) {
      notesLogger.debug('[NOTIFIER]   Sending mention notifications...');
      // Add IDs to alreadyNotifiedUserIds BEFORE calling notifyMentionedUsers
      // to prevent duplicate notifications in block 5
      mentionedIds.forEach(id => alreadyNotifiedUserIds.add(id));

      // Use performerName if provided (for comments), otherwise use authorName
      const actualPerformerName = performerName || authorName;
      await notifyMentionedUsers(mentionedIds, noteId, authorName, actualPerformerName, noteTitle, contentText, lastCommentAt, authorId);
      notesLogger.debug('[NOTIFIER]   Mention notifications sent successfully');

      logger.info(`Mention notifications sent to ${mentionedIds.length} users`);
    } else {
      notesLogger.debug('[NOTIFIER]   No mentions found, skipping mention notifications');
    }

    // 4. Notify all staff roles (ADMINs, MANAGERs, GODs) for comments
    // - Упомянутые пользователи уже получили персональное уведомление в блоке 3
    // - Все остальные сотрудники с ролью из staff-матрицы получают общее уведомление о комментарии
    
    const targetIds = employees
      .filter(emp => {
        const isNotAuthor = emp.id !== authorId;
        const notNotifiedYet = !alreadyNotifiedUserIds.has(emp.id);
        
        // РАЗРЕШАЕМ ВСЕМ: Админам, Менеджерам и GOD
        const hasStaffRole = emp.role !== null && STAFF_NOTIFICATION_ROLES.includes(emp.role as any);
        
        return hasStaffRole && isNotAuthor && notNotifiedYet && !emp.isFired;
      })
      .map(emp => emp.id);

    notesLogger.debug('[NOTIFIER]   Final targetIds for comment notifications:', targetIds.length, 'users');

    if (targetIds.length > 0) {
      notesLogger.debug('[NOTIFIER]   Sending comment notifications to', targetIds.length, 'users');
      // Prepare data with message BEFORE calling notifyMultipleUsers
      const notificationData: any = {
        noteId,
        noteTitle: truncateTitle(noteTitle),
        authorName,
        lastCommentAt: lastCommentAt || new Date().toISOString(),
        lastCommentAuthorId: authorId,
        link: {
          path: '/dashboard/notes',
          query: { noteId },
        },
      };
      
      // Get message from formatters
      notificationData.message = getNotificationMessage(EVENTS.NOTE_COMMENTED, notificationData);
      
      await notifyMultipleUsers({
        userIds: targetIds,
        event: EVENTS.NOTE_COMMENTED,
        data: notificationData,
        targetId: noteId,
      });

      notesLogger.debug('[NOTIFIER]   Comment notifications sent successfully');
      logger.info(`Comment notification sent to ${targetIds.length} ADMINs/MANAGERs for note ${noteId} by ${authorName} (${author.role})`);
    } else {
      notesLogger.debug('[NOTIFIER]   No users to notify for comment');
    }
    
    notesLogger.debug('[NOTIFIER]   onCommentCreated completed successfully');
  } catch (error) {
    notesLogger.error('[NOTIFIER] Error in onCommentCreated:', error);
    logger.error('Error in onCommentCreated:', error);
  }
}

export async function onNoteContentUpdated(note: any, user: UserInfo): Promise<void> {
  try {
    if (!note.isPublic) return;
    const employees = await getEmployees('SYSTEM');
    const editor = employees.find(emp => emp.id === user.id);
    const editorName = editor ? editor.fullName : 'Неизвестный';

    const targetIds = employees
      .filter(emp => emp.role !== null && STAFF_NOTIFICATION_ROLES.includes(emp.role as any) && !emp.isFired && emp.id !== user.id)
      .map(emp => emp.id);

    if (targetIds.length > 0) {
      const notificationData: any = {
        noteId: note.id,
        noteTitle: truncateTitle(note.title),
        authorName: editorName,
        link: { path: '/dashboard/notes', query: { noteId: note.id } },
      };
      notificationData.message = getNotificationMessage('note:content_updated', notificationData);
      await notifyMultipleUsers({ userIds: targetIds, event: 'note:content_updated', data: notificationData, targetId: note.id });
    }
  } catch (error) { logger.error('Error in onNoteContentUpdated:', error); }
}

/**
 * Обработка найденных упоминаний
 * Принимает массив UUID вместо имен для надежного определения пользователей
 */
export async function onMentionFound(
  mentionedIds: string[],
  noteId: string,
  authorName: string,
  performerName?: string,
  noteTitle?: string,
  contentText?: string,
  lastCommentAt?: string,
  authorId?: string
): Promise<void> {
  // Use performerName if provided, otherwise fall back to authorName
  const actualPerformerName = performerName || authorName;
  await notifyMentionedUsers(mentionedIds, noteId, authorName, actualPerformerName, noteTitle, contentText, lastCommentAt, authorId);
}

/**
 * Обработка уведомлений при срабатывании персонального напоминания
 * Icon: Bell (🔔)
 * Format: Сработало напоминание по заметке '{title}'
 * Address: Only the specific user who set the reminder (personal notification)
 */
export async function onNoteReminder(
  noteId: string,
  title: string,
  userId: string
): Promise<void> {
  try {
    logger.info(`[Reminder] Sending personal reminder notification to user ${userId} for note ${noteId}`);

    // Prepare notification data
    const notificationData: any = {
      noteId,
      title,
      userId,
      noteTitle: truncateTitle(title),
      link: {
        path: '/dashboard/notes',
        query: { noteId },
      },
    };

    // Generate message
    notificationData.message = `Сработало напоминание по заметке '${truncateTitle(title)}'`;

    // Send notification ONLY to the specific user who set the reminder
    await notify({
      userId,
      event: 'note:personal_reminder',
      data: notificationData,
      targetId: noteId,
    });

    logger.info(`[Reminder] Personal reminder notification sent to user ${userId} for note ${noteId}`);
  } catch (error) {
    logger.error(`[Reminder] Error sending personal reminder notification for note ${noteId}:`, error);
  }
}
