// src/server/features/notes/lib/layout.service.ts
// Layout service for note collision resolution and positioning

import crypto from 'node:crypto';
import { db } from '@serverShared/db/client';
import { noteLayouts } from '../db/note_layouts.table';
import { notes as notesTable } from '../db/notes.table';
import { rolePermissionsTable } from '@features/system/db/role_permissions.table';
import { getEmployees } from '@features/personnel/personnel.service';
import { AppPermission } from '@shared/contracts/permissions';
import { USER_ROLES } from '@shared/constants/roles';
import { eq, and, isNull } from 'drizzle-orm';
import { logger } from '@serverShared/lib/logger';
import { updateNoteLayout, findLayout } from '../db/notes.repository';
import type { UserInfo } from '@serverShared/lib/auth';
import type { UpdateNoteInput } from '../notes.service';
import { dayjs } from '@serverShared/lib/dayjs';

const DEFAULT_HEIGHT = 220;

/**
 * Находит лучшую свободную колонку.
 * Делает SELECT только из note_layouts по конкретному userId.
 * @param excludeNoteId - ID заметки, которую нужно игнорировать (чтобы не спотыкаться о саму себя)
 */
export async function findBestPosition(
  userId: string,
  columnCount: number = 6,
  excludeNoteId?: string
): Promise<{ column: number; positionY: number }> {
  try {
    // 1. Получаем все layout записи для пользователя
    const userLayouts = await db
      .select({
        noteId: noteLayouts.noteId,
        column: noteLayouts.column,
        positionY: noteLayouts.positionY,
        height: noteLayouts.height,
      })
      .from(noteLayouts)
      .where(eq(noteLayouts.userId, userId));

    // 2. Исключаем текущую заметку (если мы её только что создали)
    let effectiveLayouts = userLayouts;
    if (excludeNoteId) {
      effectiveLayouts = userLayouts.filter(l => l.noteId !== excludeNoteId);
    }

    // 3. Фильтруем только активные заметки (нужно JOIN с notes table)
    if (effectiveLayouts.length > 0) {
      const activeNoteIds = await db
        .select({ id: notesTable.id })
        .from(notesTable)
        .where(
          and(
            eq(notesTable.status, 'active'),
            isNull(notesTable.archivedAt)
          )
        );
      
      const activeIdsSet = new Set(activeNoteIds.map(n => n.id));
      effectiveLayouts = effectiveLayouts.filter(l => activeIdsSet.has(l.noteId));
    }

    // 4. Считаем высоту каждой колонки
    const columnBottoms: number[] = new Array(columnCount).fill(0);

    effectiveLayouts.forEach(layout => {
      const colIndex = Number(layout.column || 1) - 1;
      
      if (colIndex >= 0 && colIndex < columnCount) {
        // Дно = Y + Высота + Отступ
        const bottom = Number(layout.positionY || 0) + Number(layout.height || DEFAULT_HEIGHT) + 10;
        
        // Находим самую нижнюю точку в этой колонке
        if (bottom > columnBottoms[colIndex]) {
          columnBottoms[colIndex] = bottom;
        }
      }
    });

    // 5. Ищем колонку с МИНИМАЛЬНОЙ высотой
    let minBottom = columnBottoms[0];
    let bestColumn = 1;

    for (let i = 1; i < columnCount; i++) {
      if (columnBottoms[i] < minBottom) {
        minBottom = columnBottoms[i];
        bestColumn = i + 1;
      }
    }

    // Логируем решение (поможет понять, почему выбрана колонка)
    logger.info(`[Layout] User ${userId.slice(0, 4)}...: Cols [${columnBottoms.join(', ')}] -> Best: Col ${bestColumn} @ ${minBottom}px`);

    return { column: bestColumn, positionY: minBottom };
  } catch (error) {
    logger.error('Error finding best position:', error);
    return { column: 1, positionY: 0 };
  }
}

export async function deleteLayoutsForNote(noteId: string): Promise<void> {
  await db
    .delete(noteLayouts)
    .where(eq(noteLayouts.noteId, noteId));

  logger.info(`[Layout] Deleted all layouts for note ${noteId}`);
}

export async function initializeLayoutsForPrivateNote(
  noteId: string,
  authorId: string,
  height: number = DEFAULT_HEIGHT
): Promise<void> {
  const effectiveHeight = height || DEFAULT_HEIGHT;
  const bestPos = await findBestPosition(authorId, 6, noteId);

  await updateNoteLayout(
    noteId,
    authorId,
    bestPos.column,
    bestPos.positionY,
    effectiveHeight
  );

  logger.info(`[Layout] Initialized private layout for note ${noteId}, user ${authorId}`);
}

export async function initializeLayoutsForPublicNote(
  noteId: string,
  authorId: string,
  height: number = DEFAULT_HEIGHT
): Promise<void> {
  const [resource, action] = AppPermission.NOTES_VIEW_PUBLIC_BOARD.split(':');

  const permittedRolesRecord = await db
    .select({ role: rolePermissionsTable.role })
    .from(rolePermissionsTable)
    .where(
      and(
        eq(rolePermissionsTable.resource, resource),
        eq(rolePermissionsTable.action, action),
        eq(rolePermissionsTable.isEnabled, true)
      )
    );

  const permittedRoles = new Set(permittedRolesRecord.map((p) => p.role));
  permittedRoles.add(USER_ROLES.GOD);

  const employees = await getEmployees('SYSTEM');
  const targetUsers = employees.filter((emp) => {
    if (emp.isFired) return false;
    if (emp.id === authorId) return true;
    if (emp.role && permittedRoles.has(emp.role)) return true;
    return false;
  });

  const effectiveHeight = height || DEFAULT_HEIGHT;

  for (const user of targetUsers) {
    const existingLayout = await findLayout(noteId, user.id);
    if (existingLayout) {
      continue;
    }

    const bestPos = await findBestPosition(user.id, 6, noteId);
    await updateNoteLayout(
      noteId,
      user.id,
      bestPos.column,
      bestPos.positionY,
      effectiveHeight
    );
  }

  logger.info(`[Layout] Initialized public layouts for note ${noteId}, users=${targetUsers.length}`);
}

/**
 * @deprecated Используйте initializeLayoutsForPublicNote().
 */
export async function distributeNoteToAllUsers(
  noteId: string,
  height: number,
  authorId: string
): Promise<void> {
  await initializeLayoutsForPublicNote(noteId, authorId, height);
}

// Resolve note collisions in a column
// When a note is moved or created, check for overlapping notes in same column
// If there's an overlap, place the new/moved note below the existing one (+5px gap)
// Iteratively checks all overlaps to find the correct position
// IMPORTANT: Now uses note_layouts table for personal positioning per user
export async function resolveNoteCollisions(
  noteId: string | null,
  column: number,
  positionY: number,
  height: number,
  userId: string,
  statusFilter?: 'active' | 'done' | 'cancelled' | null
): Promise<{ positionY: number; height: number }> {
  try {
    let currentPositionY = positionY;
    let currentHeight = height || DEFAULT_HEIGHT;
    const maxIterations = 100; // Защита от бесконечного цикла
    let iteration = 0;

    logger.info(`resolveNoteCollisions: Starting for note ${noteId}, column=${column}, initialPositionY=${positionY}, height=${currentHeight}, userId=${userId}, statusFilter=${statusFilter || 'none'}`);

    while (iteration < maxIterations) {
      iteration++;
      const noteEnd = currentPositionY + currentHeight;
      
      // Get all note_layouts in the same column for the same user (excluding the current note if updating)
      const columnLayouts = await db
        .select({
          noteId: noteLayouts.noteId,
          positionY: noteLayouts.positionY,
          height: noteLayouts.height,
        })
        .from(noteLayouts)
        .where(
          and(
            eq(noteLayouts.column, column),
            eq(noteLayouts.userId, userId)
          )
        );

      // Filter out the current note if updating
      let otherLayouts = columnLayouts.filter(l => l.noteId !== noteId);
      
      if (statusFilter) {
        // Get notes with the specified status - need to JOIN with notes table
        const notes = await db
          .select({ id: notesTable.id })
          .from(noteLayouts)
          .innerJoin(notesTable, eq(noteLayouts.noteId, notesTable.id))
          .where(
            and(
              eq(noteLayouts.column, column),
              eq(noteLayouts.userId, userId),
              eq(notesTable.status, statusFilter),
              isNull(notesTable.archivedAt)
            )
          );
        const statusNoteIds = new Set(notes.map(n => n.id));
        otherLayouts = otherLayouts.filter(l => statusNoteIds.has(l.noteId));
        logger.info(`resolveNoteCollisions: Iteration ${iteration}, filtered ${columnLayouts.length} layouts to ${otherLayouts.length} layouts with status=${statusFilter}`);
      }

      // Check for overlaps
      let hasOverlap = false;
      for (const otherLayout of otherLayouts) {
        const otherStart = otherLayout.positionY;
        const otherEnd = otherStart + otherLayout.height;
        
        // Check for overlap
        if (!(noteEnd <= otherStart || currentPositionY >= otherEnd)) {
          // Overlap detected - place below the other note
          logger.info(`resolveNoteCollisions: Iteration ${iteration}, overlap detected with note ${otherLayout.noteId} (positionY=${otherStart}, height=${otherLayout.height}), moving from ${currentPositionY} to ${otherEnd + 10}`);
          currentPositionY = otherEnd + 10;
          hasOverlap = true;
          break;
        }
      }

      // No collision detected
      if (!hasOverlap) {
        logger.info(`resolveNoteCollisions: Resolved in ${iteration} iterations, final positionY=${currentPositionY}`);
        return { positionY: currentPositionY, height: currentHeight };
      }
    }

    logger.error(`resolveNoteCollisions: Max iterations (${maxIterations}) reached for note ${noteId}, column=${column}, finalPositionY=${currentPositionY}`);
    return { positionY: currentPositionY, height: currentHeight };
  } catch (error) {
    logger.error('Error resolving note collisions:', error);
    throw new Error('Failed to resolve note collisions');
  }
}

/**
 * Handle coordinate updates for a note (for ALL users)
 * NOTE: column and positionY are no longer accepted from client input.
 * They are computed server-side via findBestPosition/resolveNoteCollisions.
 * Only height and reminderAt can be updated from client input.
 */
export async function handleCoordinateUpdate(
  id: string,
  input: UpdateNoteInput,
  existingNote: any,
  user: UserInfo
) {
  const hasHeightUpdate = input.height !== undefined;
  const hasReminderUpdate = input.reminderAt !== undefined;

  if (!hasHeightUpdate && !hasReminderUpdate) return;

  // Get current personal layout for the user (backup values)
  const currentLayout = await findLayout(id, user.id);

  // Use backup values from personal layout if available, otherwise use defaults
  const backupColumn = currentLayout ? currentLayout.column : 1;
  const backupPositionY = currentLayout ? currentLayout.positionY : 0;
  const backupHeight = currentLayout ? currentLayout.height : DEFAULT_HEIGHT;

  logger.info(`handleCoordinateUpdate: Backup values for note ${id}, userId=${user.id}, column=${backupColumn}, positionY=${backupPositionY}, height=${backupHeight}, reminderAt=${input.reminderAt}`);

  // Resolve collisions if height is being updated (position stays the same)
  const resolvedPosition = await resolveNoteCollisions(
    id,
    backupColumn, // Keep current column
    backupPositionY, // Keep current position
    input.height ?? backupHeight, // Use new height or backup
    user.id, // Use user.id (who is moving) for personal layout
    input.status ?? existingNote.status // Use note's status for filtering
  );

  // Update personal layout using INSERT ... ON DUPLICATE KEY UPDATE
  await updateNoteLayout(
    id,
    user.id,
    backupColumn, // Keep current column
    resolvedPosition.positionY, // Use resolved position
    resolvedPosition.height,
    input.reminderAt
  );

  logger.info(`handleCoordinateUpdate: Updated personal layout for note ${id}, userId=${user.id}, column=${backupColumn}, positionY=${resolvedPosition.positionY}, reminderAt=${input.reminderAt}`);
}
