import { Op } from 'sequelize';
import { WorkNote } from '@models/WorkNote';
import { WorkNoteAttachment } from '@models/WorkNoteAttachment';
import { Participant } from '@models/Participant';
import { WorkflowRequest } from '@models/WorkflowRequest';
import { User } from '@models/User';
import { ApprovalLevel } from '@models/ApprovalLevel';
import { activityService } from './activity.service';
import { notificationService } from './notification.service';
import { emailNotificationService } from './emailNotification.service';
import { gcsStorageService } from './gcsStorage.service';
import logger from '@utils/logger';
import fs from 'fs';
import path from 'path';
import { sanitizeStrict, sanitizePermissive, isHtmlEmpty } from '../utils/sanitizer';

export class WorkNoteService {
  async list(requestId: string) {
    const notes = await WorkNote.findAll({
      where: { requestId },
      order: [['created_at' as any, 'ASC']]
    });

    // Load attachments for each note
    const enriched = await Promise.all(notes.map(async (note) => {
      const noteId = (note as any).noteId;
      const attachments = await WorkNoteAttachment.findAll({
        where: { noteId }
      });

      const noteData = (note as any).toJSON();

      const mappedAttachments = attachments.map((a: any) => {
        const attData = typeof a.toJSON === 'function' ? a.toJSON() : a;
        return {
          attachmentId: attData.attachmentId || attData.attachment_id,
          fileName: attData.fileName || attData.file_name,
          fileType: attData.fileType || attData.file_type,
          fileSize: attData.fileSize || attData.file_size,
          filePath: attData.filePath || attData.file_path,
          storageUrl: attData.storageUrl || attData.storage_url,
          isDownloadable: attData.isDownloadable || attData.is_downloadable,
          uploadedAt: attData.uploadedAt || attData.uploaded_at
        };
      });

      return {
        noteId: noteData.noteId || noteData.note_id,
        requestId: noteData.requestId || noteData.request_id,
        userId: noteData.userId || noteData.user_id,
        userName: noteData.userName || noteData.user_name,
        userRole: noteData.userRole || noteData.user_role,
        message: noteData.message,
        isPriority: noteData.isPriority || noteData.is_priority,
        hasAttachment: noteData.hasAttachment || noteData.has_attachment,
        createdAt: noteData.createdAt || noteData.created_at,
        updatedAt: noteData.updatedAt || noteData.updated_at,
        attachments: mappedAttachments
      };
    }));

    return enriched;
  }

  async getUserRole(requestId: string, userId: string): Promise<string> {
    try {
      const participant = await Participant.findOne({
        where: { requestId, userId }
      });
      if (participant) {
        const type = (participant as any).participantType || (participant as any).participant_type;
        return type ? type.toString() : 'Participant';
      }
      return 'Participant';
    } catch (error) {
      logger.error('[WorkNote] Error fetching user role:', error);
      return 'Participant';
    }
  }

  async create(requestId: string, user: { userId: string; name?: string; role?: string }, payload: { message: string; isPriority?: boolean; parentNoteId?: string | null; mentionedUsers?: string[] | null; }, files?: Array<{ path?: string | null; buffer?: Buffer; originalname: string; mimetype: string; size: number }>, requestMetadata?: { ipAddress?: string | null; userAgent?: string | null }): Promise<any> {
    logger.info('[WorkNote] Creating note:', { requestId, user, messageLength: payload.message?.length });

    const sanitizedMessage = sanitizeStrict(payload.message);
    if (isHtmlEmpty(sanitizedMessage)) {
      throw new Error('Message is required and must contain valid text');
    }

    const note = await WorkNote.create({
      requestId,
      userId: user.userId,
      userName: user.name || null,
      userRole: user.role || null, // Store participant type (INITIATOR/APPROVER/SPECTATOR)
      message: sanitizedMessage,
      isPriority: !!payload.isPriority,
      parentNoteId: payload.parentNoteId || null,
      mentionedUsers: payload.mentionedUsers || null,
      hasAttachment: files && files.length > 0 ? true : false
    } as any);

    logger.info('[WorkNote] Created note:', {
      noteId: (note as any).noteId,
      userId: (note as any).userId,
      userName: (note as any).userName,
      userRole: (note as any).userRole
    });

    const attachments = [];
    if (files && files.length) {
      // Get request number for folder structure
      const workflow = await WorkflowRequest.findOne({ where: { requestId } });
      const requestNumber = workflow ? ((workflow as any).requestNumber || (workflow as any).request_number) : null;

      for (const f of files) {
        // Read file buffer if path exists, otherwise use provided buffer
        const fileBuffer = f.buffer || (f.path ? fs.readFileSync(f.path) : Buffer.from(''));

        // Upload with automatic fallback to local storage
        // If requestNumber is not available, use a default structure
        const effectiveRequestNumber = requestNumber || 'UNKNOWN';
        const uploadResult = await gcsStorageService.uploadFileWithFallback({
          buffer: fileBuffer,
          originalName: f.originalname,
          mimeType: f.mimetype,
          requestNumber: effectiveRequestNumber,
          fileType: 'attachments'
        });

        const storageUrl = uploadResult.storageUrl;
        const gcsFilePath = uploadResult.filePath;

        // Clean up local temporary file if it exists (from multer disk storage)
        if (f.path && fs.existsSync(f.path)) {
          try {
            fs.unlinkSync(f.path);
          } catch (unlinkError) {
            logger.warn('[WorkNote] Failed to delete local temporary file:', unlinkError);
          }
        }

        const attachment = await WorkNoteAttachment.create({
          noteId: (note as any).noteId,
          fileName: f.originalname,
          fileType: f.mimetype,
          fileSize: f.size,
          filePath: gcsFilePath, // Store GCS path or local path
          storageUrl: storageUrl, // Store GCS URL or local URL
          isDownloadable: true
        } as any);

        attachments.push({
          attachmentId: (attachment as any).attachmentId,
          fileName: (attachment as any).fileName,
          fileType: (attachment as any).fileType,
          fileSize: (attachment as any).fileSize,
          filePath: (attachment as any).filePath,
          storageUrl: (attachment as any).storageUrl,
          isDownloadable: (attachment as any).isDownloadable
        });
      }

      // Send notifications for additional document added via work notes
      if (attachments.length > 0) {
        try {
          const workflow = await WorkflowRequest.findOne({ where: { requestId } });
          if (workflow) {
            const initiatorId = (workflow as any).initiatorId || (workflow as any).initiator_id;
            const isInitiator = user.userId === initiatorId;

            // Get all participants (spectators)
            const spectators = await Participant.findAll({
              where: {
                requestId,
                participantType: 'SPECTATOR'
              },
              include: [{
                model: User,
                as: 'user',
                attributes: ['userId', 'email', 'displayName']
              }]
            });

            // Get current approver (pending or in-progress approval level)
            const currentApprovalLevel = await ApprovalLevel.findOne({
              where: {
                requestId,
                status: { [Op.in]: ['PENDING', 'IN_PROGRESS'] }
              },
              order: [['levelNumber', 'ASC']],
              include: [{
                model: User,
                as: 'approver',
                attributes: ['userId', 'email', 'displayName']
              }]
            });

            // Determine who to notify based on who uploaded
            const recipientsToNotify: Array<{ userId: string; email: string; displayName: string }> = [];

            if (isInitiator) {
              // Initiator added → notify spectators and current approver
              spectators.forEach((spectator: any) => {
                const spectatorUser = spectator.user || spectator.User;
                if (spectatorUser && spectatorUser.userId !== user.userId) {
                  recipientsToNotify.push({
                    userId: spectatorUser.userId,
                    email: spectatorUser.email,
                    displayName: spectatorUser.displayName || spectatorUser.email
                  });
                }
              });

              if (currentApprovalLevel) {
                const approverUser = (currentApprovalLevel as any).approver || (currentApprovalLevel as any).Approver;
                if (approverUser && approverUser.userId !== user.userId) {
                  recipientsToNotify.push({
                    userId: approverUser.userId,
                    email: approverUser.email,
                    displayName: approverUser.displayName || approverUser.email
                  });
                }
              }
            } else {
              // Check if uploader is a spectator
              const uploaderParticipant = await Participant.findOne({
                where: {
                  requestId,
                  userId: user.userId,
                  participantType: 'SPECTATOR'
                }
              });

              if (uploaderParticipant) {
                // Spectator added → notify initiator and current approver
                const initiator = await User.findByPk(initiatorId);
                if (initiator) {
                  const initiatorData = initiator.toJSON();
                  if (initiatorData.userId !== user.userId) {
                    recipientsToNotify.push({
                      userId: initiatorData.userId,
                      email: initiatorData.email,
                      displayName: initiatorData.displayName || initiatorData.email
                    });
                  }
                }

                if (currentApprovalLevel) {
                  const approverUser = (currentApprovalLevel as any).approver || (currentApprovalLevel as any).Approver;
                  if (approverUser && approverUser.userId !== user.userId) {
                    recipientsToNotify.push({
                      userId: approverUser.userId,
                      email: approverUser.email,
                      displayName: approverUser.displayName || approverUser.email
                    });
                  }
                }
              } else {
                // Approver added → notify initiator and spectators
                const initiator = await User.findByPk(initiatorId);
                if (initiator) {
                  const initiatorData = initiator.toJSON();
                  if (initiatorData.userId !== user.userId) {
                    recipientsToNotify.push({
                      userId: initiatorData.userId,
                      email: initiatorData.email,
                      displayName: initiatorData.displayName || initiatorData.email
                    });
                  }
                }

                spectators.forEach((spectator: any) => {
                  const spectatorUser = spectator.user || spectator.User;
                  if (spectatorUser && spectatorUser.userId !== user.userId) {
                    recipientsToNotify.push({
                      userId: spectatorUser.userId,
                      email: spectatorUser.email,
                      displayName: spectatorUser.displayName || spectatorUser.email
                    });
                  }
                });
              }
            }

            // Send notifications (email, in-app, and web-push)
            const requestNumber = (workflow as any).requestNumber || requestId;
            const requestData = {
              requestNumber: requestNumber,
              requestId: requestId,
              title: (workflow as any).title || 'Request'
            };

            // Prepare user IDs for in-app and web-push notifications
            const recipientUserIds = recipientsToNotify.map(r => r.userId);

            // Send in-app and web-push notifications for each attachment
            if (recipientUserIds.length > 0 && attachments.length > 0) {
              try {
                for (const attachment of attachments) {
                  await notificationService.sendToUsers(
                    recipientUserIds,
                    {
                      title: 'Additional Document Added',
                      body: `${user.name || 'User'} added "${attachment.fileName}" to ${requestNumber}`,
                      requestId,
                      requestNumber,
                      url: `/request/${requestNumber}`,
                      type: 'document_added',
                      priority: 'MEDIUM',
                      actionRequired: false,
                      metadata: {
                        documentName: attachment.fileName,
                        fileSize: attachment.fileSize,
                        addedByName: user.name || 'User',
                        source: 'Work Notes'
                      }
                    }
                  );
                }
                logger.info('[WorkNote] In-app and web-push notifications sent for additional documents', {
                  requestId,
                  attachmentsCount: attachments.length,
                  recipientsCount: recipientUserIds.length
                });
              } catch (notifyError) {
                logger.error('[WorkNote] Failed to send in-app/web-push notifications for additional documents:', notifyError);
              }
            }

            // Send email notifications for each attachment
            for (const attachment of attachments) {
              for (const recipient of recipientsToNotify) {
                await emailNotificationService.sendAdditionalDocumentAdded(
                  requestData,
                  recipient,
                  {
                    documentName: attachment.fileName,
                    fileSize: attachment.fileSize,
                    addedByName: user.name || 'User',
                    source: 'Work Notes'
                  }
                );
              }
            }

            logger.info('[WorkNote] Additional document notifications sent', {
              requestId,
              attachmentsCount: attachments.length,
              recipientsCount: recipientsToNotify.length,
              isInitiator
            });
          }
        } catch (notifyError) {
          // Don't fail work note creation if notifications fail
          logger.error('[WorkNote] Failed to send additional document notifications:', notifyError);
        }
      }
    }

    // Log activity for work note
    activityService.log({
      requestId,
      type: 'comment',
      user: { userId: user.userId, name: user.name || 'User' },
      timestamp: new Date().toISOString(),
      action: 'Work Note Added',
      details: `${user.name || 'User'} added a work note: ${payload.message.substring(0, 100)}${payload.message.length > 100 ? '...' : ''}`,
      ipAddress: requestMetadata?.ipAddress || undefined,
      userAgent: requestMetadata?.userAgent || undefined
    });

    try {
      // Optional realtime emit (if socket layer is initialized)
      const { emitToRequestRoom } = require('../realtime/socket');
      if (emitToRequestRoom) {
        // Emit note with all fields explicitly (to ensure camelCase fields are sent)
        const noteData = {
          noteId: (note as any).noteId,
          requestId: (note as any).requestId,
          userId: (note as any).userId,
          userName: (note as any).userName,
          userRole: (note as any).userRole, // Include participant role
          message: (note as any).message,
          createdAt: (note as any).createdAt,
          hasAttachment: (note as any).hasAttachment,
          attachments: attachments // Include attachments
        };
        emitToRequestRoom(requestId, 'worknote:new', { note: noteData });
      }
    } catch (e) { logger.warn('Realtime emit failed (not initialized)'); }

    // Send notifications to mentioned users
    if (payload.mentionedUsers && Array.isArray(payload.mentionedUsers) && payload.mentionedUsers.length > 0) {
      try {
        // Get workflow details for request number and title
        const workflow = await WorkflowRequest.findOne({ where: { requestId } });
        const requestNumber = (workflow as any)?.requestNumber || requestId;
        const requestTitle = (workflow as any)?.title || 'Request';

        logger.info(`[WorkNote] Sending mention notifications to ${payload.mentionedUsers.length} users`);

        await notificationService.sendToUsers(
          payload.mentionedUsers,
          {
            title: '💬 Mentioned in Work Note',
            body: `${user.name || 'Someone'} mentioned you in ${requestNumber}: "${payload.message.substring(0, 50)}${payload.message.length > 50 ? '...' : ''}"`,
            requestId,
            requestNumber,
            url: `/request/${requestNumber}`,
            type: 'mention'
          }
        );

        logger.info(`[WorkNote] Mention notifications sent successfully`);
      } catch (notifyError) {
        logger.error('[WorkNote] Failed to send mention notifications:', notifyError);
        // Don't fail the work note creation if notifications fail
      }
    }

    return { ...note, attachments };
  }

  async downloadAttachment(attachmentId: string) {
    const attachment = await WorkNoteAttachment.findOne({
      where: { attachmentId }
    });

    if (!attachment) {
      throw new Error('Attachment not found');
    }

    const storageUrl = (attachment as any).storageUrl || (attachment as any).storage_url;
    const filePath = (attachment as any).filePath || (attachment as any).file_path;
    const fileName = (attachment as any).fileName || (attachment as any).file_name;
    const fileType = (attachment as any).fileType || (attachment as any).file_type;

    // Check if it's a GCS URL
    const isGcsUrl = storageUrl && (storageUrl.startsWith('https://storage.googleapis.com') || storageUrl.startsWith('gs://'));

    return {
      filePath: filePath,
      storageUrl: storageUrl,
      fileName: fileName,
      fileType: fileType,
      isGcsUrl: isGcsUrl
    };
  }
}

export const workNoteService = new WorkNoteService();


