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

import crypto from 'node:crypto';
import { db } from '../../../shared/db/client';
import { noteLayouts } from '../db/note_layouts.table';
import { notes as notesTable } from '../db/notes.table';
import { users } from '../../personnel/db/employees.table';
import { eq, and, ne, isNull } from 'drizzle-orm';
import { logger } from '../../../shared/lib/logger';
import { findWithLayout, updateNoteLayout, findLayout } from '../db/notes.repository';
import type { UserInfo } from '../../../shared/lib/auth';
import type { UpdateNoteInput } from '../notes.service';
import { dayjs } from '../../../shared/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 };
  }
}

/**
 * Distributes a note to ALL active users (NOT FIRED).
 * NO TRY/CATCH wrapping here - let the caller handle errors to see them!
 */
export async function distributeNoteToAllUsers(
  noteId: string,
  height: number,
  authorId: string
): Promise<void> {
  // 1. Fetch all NOT FIRED users (isFired = false)
  // Игнорируем archivedAt, так как это тестовые данные
  const allUsers = await db
    .select({ id: users.id })
    .from(users)
    .where(
      and(
        eq(users.isFired, false),
        ne(users.id, authorId)
      )
    );

  logger.info(`[Layout] Distributing note ${noteId}. Found ${allUsers.length} target users (isFired=false).`);

  if (allUsers.length === 0) {
    logger.warn('[Layout] No users to distribute to.');
    return;
  }

  const now = dayjs().toDate();
  const effectiveHeight = height || DEFAULT_HEIGHT;

  // 2. Calculate positions in PARALLEL
  const layoutPayloads = await Promise.all(
    allUsers.map(async (user) => {
      // Pass noteId to exclude it from calculation
      const bestPos = await findBestPosition(user.id, 6, noteId);
      
      return {
        id: crypto.randomUUID(),
        noteId,
        userId: user.id,
        column: bestPos.column,
        positionY: bestPos.positionY,
        height: effectiveHeight,
        createdAt: now,
        updatedAt: now,
      };
    })
  );

  // 3. Bulk Insert
  if (layoutPayloads.length > 0) {
    for (const payload of layoutPayloads) {
      await db.insert(noteLayouts)
        .values(payload)
        .onDuplicateKeyUpdate({
          set: {
            column: payload.column,
            positionY: payload.positionY,
            height: payload.height,
            updatedAt: now
          }
        });
    }
    logger.info(`[Layout] SUCCESS: Inserted/Updated ${layoutPayloads.length} layout records.`);
  }
}

// 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}`);
}
