// src/server/features/notes/lib/history.service.ts
// History service for creating note history entries

import { db } from '../../../shared/db/client';
import { notes } from '../db/notes.table';
import { eq } from 'drizzle-orm';
import { loggerService } from '../../../shared/lib/logger';
import { dayjs } from '../../../shared/lib/dayjs';
import { createHistoryEntry as createHistoryEntryRepo } from '../db/notes.repository';
import type { UserInfo } from '../../../shared/lib/auth';
import type { UpdateNoteInput } from '../notes.service';
import { emitNoteUpdatedEvent } from '../notes.events';
import { notifyAdmins, notifyManagers, notifyUser } from '../../../shared/plugins/socket';

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

export type NoteHistoryPayload = {
  oldStatus?: string;
  newStatus?: string;
  commentText?: string;
  reason?: string;
  changes?: Record<string, any>;
};

/**
 * Create a history entry for a note
 */
export async function createHistoryEntry(
  noteId: string,
  action: 'created' | 'updated' | 'status_changed' | 'commented' | 'returned' | 'priority_changed',
  payload: NoteHistoryPayload,
  authorId: string
) {
  await createHistoryEntryRepo(noteId, action, payload, authorId);
}

/**
 * Create a history entry for content update (title or content changed)
 */
export async function createContentUpdateHistory(
  noteId: string,
  titleChanged: boolean,
  contentChanged: boolean,
  authorId: string,
  oldTitle?: string,
  newTitle?: string,
  oldContent?: string,
  newContent?: string
) {
  const changes: Record<string, any> = {
    titleChanged,
    contentChanged,
  };

  // Add actual text values for changed fields
  if (titleChanged) {
    changes.oldTitle = oldTitle;
    changes.newTitle = newTitle;
  }
  if (contentChanged) {
    changes.oldContent = oldContent;
    changes.newContent = newContent;
  }

  await createHistoryEntry(
    noteId,
    'updated',
    {
      changes,
    },
    authorId
  );
}

/**
 * Create a history entry for status change
 */
export async function createStatusChangeHistory(
  noteId: string,
  oldStatus: string,
  newStatus: string,
  commentText?: string,
  authorId?: string
) {
  if (!authorId) return;
  
  await createHistoryEntry(
    noteId,
    'status_changed',
    {
      oldStatus,
      newStatus,
      commentText,
    },
    authorId
  );
}

/**
 * Create a history entry for comment
 */
export async function createCommentHistory(
  noteId: string,
  commentText: string,
  authorId: string
) {
  await createHistoryEntry(
    noteId,
    'commented',
    {
      commentText,
    },
    authorId
  );
}

/**
 * Create a history entry for priority change
 */
export async function createPriorityChangeHistory(
  noteId: string,
  oldPriority: string,
  newPriority: string,
  authorId: string
) {
  await createHistoryEntry(
    noteId,
    'priority_changed',
    {
      changes: {
        oldPriority,
        newPriority,
      },
    },
    authorId
  );
}

/**
 * Handle content updates for a note (for ADMIN or private notes)
 */
export async function handleContentUpdate(
  id: string,
  input: UpdateNoteInput,
  existingNote: any,
  sanitizedTitle: string | undefined,
  sanitizedContent: string | undefined,
  user?: UserInfo,
  priorityChanged?: boolean
) {
  notesLogger.debug('[DEBUG] handleContentUpdate called with id:', id, 'input:', input);
  notesLogger.debug('[DEBUG] handleContentUpdate: existingNote.status:', existingNote.status);
  
  // Build content update object (excluding coordinates)
  const contentUpdate: any = {};
  if (sanitizedTitle !== undefined) contentUpdate.title = sanitizedTitle;
  if (sanitizedContent !== undefined) contentUpdate.content = sanitizedContent;
  if (input.priority !== undefined) contentUpdate.priority = input.priority;
  if (input.status !== undefined) contentUpdate.status = input.status;
  if (input.isPublic !== undefined) contentUpdate.isPublic = input.isPublic;
  if (input.userId !== undefined) contentUpdate.userId = input.userId;
  if (input.spaceId !== undefined) contentUpdate.spaceId = input.spaceId;
  // Include priorityUpdatedAt if it was set in updateNote
  if (input.priorityUpdatedAt !== undefined) {
    contentUpdate.priorityUpdatedAt = input.priorityUpdatedAt;
  }
  if (input.lastPriorityAuthorId !== undefined) {
    contentUpdate.lastPriorityAuthorId = input.lastPriorityAuthorId;
  }

  notesLogger.debug('[DEBUG] handleContentUpdate: contentUpdate:', contentUpdate);

  // Only update notes table if there are content changes
  if (Object.keys(contentUpdate).length === 0) {
    notesLogger.debug('[DEBUG] handleContentUpdate: No content changes, returning');
    return;
  }

  contentUpdate.updatedAt = new Date();

  notesLogger.debug('[DEBUG] handleContentUpdate: Updating note in database with:', contentUpdate);
  await db
    .update(notes)
    .set(contentUpdate)
    .where(eq(notes.id, id));
  notesLogger.debug('[DEBUG] handleContentUpdate: Database update completed');

  // Track changes for event emission
  const changes = {
    contentUpdated: sanitizedTitle !== undefined && sanitizedTitle !== existingNote.title ||
                   sanitizedContent !== undefined && sanitizedContent !== existingNote.content,
    statusChanged: input.status !== undefined && input.status !== existingNote.status,
    isPublicChanged: input.isPublic !== undefined && input.isPublic !== existingNote.isPublic,
    priorityChanged: priorityChanged ?? (input.priority !== undefined && input.priority !== existingNote.priority),
    oldStatus: existingNote.status,
    oldPriority: existingNote.priority,
    newIsPublic: input.isPublic,
  };

  // Create history entry for content update (title or content changed)
  // Only create history if content actually changed (not just undefined values)
  if (changes.contentUpdated && user) {
    const titleChanged = sanitizedTitle !== undefined && sanitizedTitle !== existingNote.title;
    const contentChanged = sanitizedContent !== undefined && sanitizedContent !== existingNote.content;
    
    await createContentUpdateHistory(
      id,
      titleChanged,
      contentChanged,
      user.id,
      titleChanged ? existingNote.title : undefined,
      titleChanged ? sanitizedTitle : undefined,
      contentChanged ? existingNote.content : undefined,
      contentChanged ? sanitizedContent : undefined
    );
  }

  // Create history entry for status change with comment
  if (changes.statusChanged && user) {
    await createStatusChangeHistory(
      id,
      existingNote.status,
      input.status!,
      input.comment,
      user.id
    );
  }

  // Create history entry for priority change - only for public notes
  if (changes.priorityChanged && user && existingNote.isPublic) {
    await createPriorityChangeHistory(id, changes.oldPriority, input.priority!, user.id);
  }

  // Fetch updated note for socket event
  const updatedNote = await (async () => {
    const result = await db
      .select()
      .from(notes)
      .where(eq(notes.id, id))
      .limit(1);
    return result[0];
  })();

  // Transform updatedNote to match contract with ISO string dates
  const noteForEvent = {
    ...updatedNote,
    createdAt: dayjs.utc(updatedNote.createdAt).toISOString(),
    updatedAt: dayjs.utc(updatedNote.updatedAt).toISOString(),
    archivedAt: updatedNote.archivedAt ? dayjs.utc(updatedNote.archivedAt).toISOString() : null,
    lastCommentAt: updatedNote.lastCommentAt ? dayjs.utc(updatedNote.lastCommentAt).toISOString() : null,
    priorityUpdatedAt: updatedNote.priorityUpdatedAt ? dayjs.utc(updatedNote.priorityUpdatedAt).toISOString() : null,
    lastMentionAt: null,
    layout: null, // Layout is handled separately via note_layouts table
  };

  // Emit note:updated event (author name lookup will happen in event listener)
  await emitNoteUpdatedEvent({
    noteId: id,
    note: noteForEvent,
    authorId: existingNote.authorId,
    performerId: user?.id,
    oldStatus: changes.statusChanged ? changes.oldStatus : undefined,
    userId: user?.id,
    contentUpdated: changes.contentUpdated,
    statusChanged: changes.statusChanged,
    isPublicChanged: changes.isPublicChanged,
    newIsPublic: changes.isPublicChanged ? changes.newIsPublic : undefined,
    priorityChanged: changes.priorityChanged,
    oldPriority: changes.priorityChanged ? changes.oldPriority : undefined,
  });

  // Emit note:updated to socket clients for real-time sync
  if (updatedNote.isPublic) {
    // Public notes: notify all admins and managers
    await notifyAdmins('note:updated', { id, updates: updatedNote });
    await notifyManagers('note:updated', { id, updates: updatedNote });
  } else {
    // Private notes: notify only the note's author
    if (existingNote.authorId) {
      await notifyUser(existingNote.authorId, 'note:updated', { id, updates: updatedNote });
    }
  }
}

/**
 * Handle MANAGER restricted updates for public notes
 */
export async function handleManagerRestrictedUpdate(
  id: string,
  input: UpdateNoteInput,
  existingNote: any,
  user: UserInfo,
  priorityChanged?: boolean
) {
  // Only allow status and priority updates
  const allowedUpdate: Partial<UpdateNoteInput> = {};
  if (input.status !== undefined) allowedUpdate.status = input.status;
  if (input.priority !== undefined) allowedUpdate.priority = input.priority;
  // Include priorityUpdatedAt if it was set in updateNote
  if (input.priorityUpdatedAt !== undefined) {
    allowedUpdate.priorityUpdatedAt = input.priorityUpdatedAt;
  }
  if (input.lastPriorityAuthorId !== undefined) {
    allowedUpdate.lastPriorityAuthorId = input.lastPriorityAuthorId;
  }

  // Only update if there are allowed content changes
  if (Object.keys(allowedUpdate).length === 0) return;

  await db
    .update(notes)
    .set({
      ...allowedUpdate,
      updatedAt: new Date(),
    })
    .where(eq(notes.id, id));

  // Track changes for event emission
  const changes = {
    statusChanged: input.status !== undefined && input.status !== existingNote.status,
    priorityChanged: priorityChanged ?? (input.priority !== undefined && input.priority !== existingNote.priority),
    oldStatus: existingNote.status,
    oldPriority: existingNote.priority,
  };

  // Create history entry for status change with comment
  if (changes.statusChanged) {
    await createStatusChangeHistory(
      id,
      existingNote.status,
      input.status!,
      input.comment,
      user.id
    );
  }

  // Create history entry for priority change - only for public notes
  if (changes.priorityChanged && existingNote.isPublic) {
    await createPriorityChangeHistory(id, changes.oldPriority, input.priority!, user.id);
  }

  // Fetch updated note for notification
  const updatedNote = await (async () => {
    const result = await db
      .select()
      .from(notes)
      .where(eq(notes.id, id))
      .limit(1);
    return result[0];
  })();

  // Transform updatedNote to match contract with ISO string dates
  const noteForEvent = {
    ...updatedNote,
    createdAt: dayjs.utc(updatedNote.createdAt).toISOString(),
    updatedAt: dayjs.utc(updatedNote.updatedAt).toISOString(),
    archivedAt: updatedNote.archivedAt ? dayjs.utc(updatedNote.archivedAt).toISOString() : null,
    lastCommentAt: updatedNote.lastCommentAt ? dayjs.utc(updatedNote.lastCommentAt).toISOString() : null,
    priorityUpdatedAt: updatedNote.priorityUpdatedAt ? dayjs.utc(updatedNote.priorityUpdatedAt).toISOString() : null,
    lastMentionAt: null,
    layout: null, // Layout is handled separately via note_layouts table
  };

  // Emit note:updated event (author name lookup will happen in event listener)
  await emitNoteUpdatedEvent({
    noteId: id,
    note: noteForEvent,
    authorId: existingNote.authorId,
    performerId: user.id,
    oldStatus: changes.statusChanged ? changes.oldStatus : undefined,
    userId: user.id,
    contentUpdated: false,
    statusChanged: changes.statusChanged,
    isPublicChanged: false,
    newIsPublic: existingNote.isPublic,
    priorityChanged: changes.priorityChanged,
    oldPriority: changes.priorityChanged ? changes.oldPriority : undefined,
  });

  // Emit note:updated to socket clients for real-time sync
  if (updatedNote.isPublic) {
    // Public notes: notify all admins and managers
    await notifyAdmins('note:updated', { id, updates: updatedNote });
    await notifyManagers('note:updated', { id, updates: updatedNote });
  } else {
    // Private notes: notify only the note's author
    if (existingNote.authorId) {
      await notifyUser(existingNote.authorId, 'note:updated', { id, updates: updatedNote });
    }
  }

  logger.info(`handleManagerRestrictedUpdate: Note updated (MANAGER restricted): ${id}`);
}
