// src/server/features/reference_books/inventory.service.ts
// Business logic for inventory management with approval workflow

import { db } from '../../shared/db/client';
import { inventoryTable, inventoryPendingTable } from './db/inventory.table';
import { employees } from '../personnel/db/employees.table';
import { eq, and, or, isNull, isNotNull } from 'drizzle-orm';
import { mapToInventoryEntry, mapToInventoryPendingEntry, mapToInventoryEntryWithMetadataStrict } from './lib/inventory.mapper';
import { createDraftInventory, updateDraftInventory, deleteDraftInventory, createDeleteRequestInventory } from './lib/inventory.drafts';
import { executeCreateInventory, executeUpdateInventory, executeDeleteInventory, executeApproveInventory, executeRejectInventory } from './lib/inventory.core';
import type { InventoryPendingEntry, InventoryEntryWithMetadata } from './inventory.schema';
import type { InventoryCreateRequest, InventoryUpdateRequest } from '@shared/contracts/inventory';

// Extended types for service layer (with isAdmin flag)
export interface CreateInventoryRequestWithAdmin extends InventoryCreateRequest {
  isAdmin: boolean;
  createdBy?: string;
}

export interface UpdateInventoryRequestWithAdmin extends Omit<InventoryUpdateRequest, 'id'> {
  isAdmin: boolean;
  updatedBy?: string;
}

// Type for inventory entry
type InventoryEntryType = typeof inventoryTable.$inferSelect;
type InventoryPendingEntryType = typeof inventoryPendingTable.$inferSelect;

/**
 * Создать запись в списке инвентаря
 */
export async function createInventoryEntry(request: CreateInventoryRequestWithAdmin): Promise<InventoryEntryWithMetadata> {
  // АДМИН: Прямое создание в таблице inventory
  if (request.isAdmin) {
    const result = await executeCreateInventory(request, request.createdBy || 'unknown');
    return mapToInventoryEntryWithMetadataStrict(result);
  }

  // МЕНЕДЖЕР: Создаем черновик на согласование
  const result = await createDraftInventory(request, request.createdBy || 'unknown');
  return mapToInventoryPendingEntry(result);
}

/**
 * Обновить запись в списке инвентаря
 */
export async function updateInventoryEntry(
  id: string,
  request: Omit<UpdateInventoryRequestWithAdmin, 'id'>
): Promise<InventoryEntryWithMetadata> {
  const { name, category, quantity, unit, location, comment, weightPerUnit, manufacturer, model, serialNumber, orderingLink, articleNumber, updatedBy, isAdmin } = request;

  // 1. Пытаемся найти в основной таблице inventory
  let existing = await db.select()
    .from(inventoryTable)
    .where(eq(inventoryTable.id, id))
    .limit(1);

  let isPendingRecord = false;
  let pendingRecord: typeof inventoryPendingTable.$inferSelect | null = null;

  // 2. Если нет в основной, ищем в таблице черновиков inventory_pending
  if (!existing[0]) {
    const pendingExisting = await db.select()
      .from(inventoryPendingTable)
      .where(eq(inventoryPendingTable.id, id))
      .limit(1);

    if (pendingExisting[0]) {
      existing = pendingExisting as any; // Мапим для совместимости проверок
      pendingRecord = pendingExisting[0]; // Сохраняем оригинальную pending запись
      isPendingRecord = true;
    }
  }

  // 3. Если не нашли ни там, ни там — кидаем ошибку
  if (!existing[0]) {
    throw new Error('Запись не найдена');
  }

  // Валидация полей
  if (name !== undefined && name.trim().length === 0) {
    throw new Error('Название обязательно');
  }
  if (category !== undefined && category.trim().length === 0) {
    throw new Error('Категория обязательна');
  }
  if (quantity !== undefined && quantity.trim().length === 0) {
    throw new Error('Количество обязательно');
  }
  if (location !== undefined && location.trim().length === 0) {
    throw new Error('Локация обязательна');
  }

  // 4. Если это ПРЯМОЕ обновление черновика (Админом или владельцем черновика)
  if (isPendingRecord) {
    // АДМИН: Автоматическое согласование при редактировании черновика
    if (isAdmin) {
      const updates: any = {
        updatedAt: new Date(),
      };
      if (name !== undefined) updates.name = name.trim();
      if (category !== undefined) updates.category = category.trim();
      if (quantity !== undefined) updates.quantity = quantity; // Без trim для чисел
      if (unit !== undefined) updates.unit = unit;
      if (location !== undefined) updates.location = location.trim();
      if (comment !== undefined) updates.comment = comment?.trim() || null;
      if (weightPerUnit !== undefined) updates.weightPerUnit = weightPerUnit; // Без trim для чисел
      if (manufacturer !== undefined) updates.manufacturer = manufacturer?.trim() || null;
      if (model !== undefined) updates.model = model?.trim() || null;
      if (serialNumber !== undefined) updates.serialNumber = serialNumber?.trim() || null;
      if (orderingLink !== undefined) updates.orderingLink = orderingLink?.trim() || null;
      if (articleNumber !== undefined) updates.articleNumber = articleNumber?.trim() || null;

      const result = await executeApproveInventory(id, updatedBy!);
      return mapToInventoryEntryWithMetadataStrict(result);
    }

    // МЕНЕДЖЕР: Обычное обновление черновика (без согласования)
    const updates: any = { updatedAt: new Date() };
    if (name !== undefined) updates.name = name.trim();
    if (category !== undefined) updates.category = category.trim();
    if (quantity !== undefined) updates.quantity = quantity; // Без trim для чисел
    if (unit !== undefined) updates.unit = unit;
    if (location !== undefined) updates.location = location.trim();
    if (comment !== undefined) updates.comment = comment?.trim() || null;
    if (weightPerUnit !== undefined) updates.weightPerUnit = weightPerUnit; // Без trim для чисел
    if (manufacturer !== undefined) updates.manufacturer = manufacturer?.trim() || null;
    if (model !== undefined) updates.model = model?.trim() || null;
    if (serialNumber !== undefined) updates.serialNumber = serialNumber?.trim() || null;
    if (orderingLink !== undefined) updates.orderingLink = orderingLink?.trim() || null;
    if (articleNumber !== undefined) updates.articleNumber = articleNumber?.trim() || null;

    const result = await updateDraftInventory({ ...updates, id, updatedBy }, existing[0]);
    return mapToInventoryPendingEntry(result);
  }

  // 5. Дальше идет старая логика для записей из основной таблицы (inventory)

  // АДМИН: Прямое обновление в таблице inventory
  if (isAdmin) {
    const result = await executeUpdateInventory(id, request);
    return mapToInventoryEntryWithMetadataStrict(result);
  }

  // МЕНЕДЖЕР: Создаем черновик на согласование
  const result = await updateDraftInventory({ ...request, id }, existing[0]);
  return mapToInventoryPendingEntry(result);
}

/**
 * Удалить запись из списка инвентаря (soft delete)
 */
export async function deleteInventoryEntry(id: string, deletedBy: string, isAdmin: boolean): Promise<void> {
  // 1. Сначала ищем в основной таблице inventory
  let existing = await db.select()
    .from(inventoryTable)
    .where(eq(inventoryTable.id, id))
    .limit(1);

  let isPendingRecord = false;

  // 2. Если не найдено в основной таблице, ищем в inventory_pending
  if (!existing[0]) {
    const pendingExisting = await db.select()
      .from(inventoryPendingTable)
      .where(eq(inventoryPendingTable.id, id))
      .limit(1);

    if (pendingExisting[0]) {
      existing = pendingExisting as any; // Мапим для совместимости
      isPendingRecord = true;
    }
  }

  // 3. Если не нашли ни там, ни там — кидаем ошибку
  if (!existing[0]) {
    throw new Error('Запись не найдена');
  }

  // 4. Если это удаление черновика (еще не одобренной записи)
  if (isPendingRecord) {
    return deleteDraftInventory(id);
  }

  // 5. АДМИН: Архивация записи из таблицы inventory
  if (isAdmin) {
    return executeDeleteInventory(id);
  }

  // 6. МЕНЕДЖЕР: Создаем запрос на удаление
  await createDeleteRequestInventory(existing[0], deletedBy);
}

/**
 * Получить все записи инвентаря
 * @param archived - если false, возвращает только активные записи; если true, возвращает только архивные; если undefined, возвращает только активные записи
 * @param status - 'all' (все записи), 'pending' (только на согласовании), 'approved' (только одобренные), undefined (только одобренные)
 */
export async function getAllInventoryEntries(archived?: boolean, status?: 'all' | 'pending' | 'approved'): Promise<InventoryEntryWithMetadata[]> {
  // Если запрашиваем только pending записи - используем отдельную функцию
  if (status === 'pending') {
    const pendingEntries = await getInventoryPendingEntries();
    // Преобразуем InventoryPendingEntry в InventoryEntryWithMetadata
    return pendingEntries.map(entry => ({
      id: entry.id,
      name: entry.name,
      category: entry.category,
      quantity: entry.quantity,
      unit: entry.unit,
      location: entry.location,
      comment: entry.comment,
      weightPerUnit: entry.weightPerUnit,
      manufacturer: entry.manufacturer,
      model: entry.model,
      serialNumber: entry.serialNumber,
      orderingLink: entry.orderingLink,
      articleNumber: entry.articleNumber,
      isApproved: false,
      status: entry.status,
      targetId: entry.targetId,
      createdBy: entry.createdBy,
      creatorName: entry.creatorName,
      reviewedBy: entry.reviewedBy,
      reviewedAt: entry.reviewedAt,
      rejectionReason: entry.rejectionReason,
      createdAt: entry.createdAt,
      updatedAt: entry.updatedAt,
      archivedAt: null, // Pending записи не архивируются
    }));
  }

  // Формируем условие для фильтрации по archivedAt
  const archivedCondition = archived === true
    ? isNotNull(inventoryTable.archivedAt) // archivedAt IS NOT NULL
    : isNull(inventoryTable.archivedAt); // archivedAt IS NULL (по умолчанию)

  // Получаем одобренные записи из основной таблицы
  const whereCondition = archivedCondition;

  const approvedEntries = await db.select()
    .from(inventoryTable)
    .where(whereCondition)
    .orderBy(inventoryTable.createdAt);

  // Если запрашиваем все записи (all), добавляем pending записи
  if (status === 'all') {
    const pendingEntries = await getInventoryPendingEntries();
    const mappedPendingEntries = pendingEntries.map(entry => ({
      id: entry.id,
      name: entry.name,
      category: entry.category,
      quantity: entry.quantity,
      unit: entry.unit,
      location: entry.location,
      comment: entry.comment,
      weightPerUnit: entry.weightPerUnit,
      manufacturer: entry.manufacturer,
      model: entry.model,
      serialNumber: entry.serialNumber,
      orderingLink: entry.orderingLink,
      articleNumber: entry.articleNumber,
      isApproved: false,
      status: entry.status,
      targetId: entry.targetId,
      createdBy: entry.createdBy,
      creatorName: entry.creatorName,
      reviewedBy: entry.reviewedBy,
      reviewedAt: entry.reviewedAt,
      rejectionReason: entry.rejectionReason,
      createdAt: entry.createdAt,
      updatedAt: entry.updatedAt,
      archivedAt: null, // Pending записи не архивируются
    }));

    // Объединяем и сортируем по createdAt
    return [...mappedPendingEntries, ...approvedEntries.map(mapToInventoryEntry)]
      .sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
  }

  // Если запрашиваем только одобренные (approved/undefined)
  return approvedEntries.map(mapToInventoryEntry);
}

/**
 * Получить запись по ID
 */
export async function getInventoryEntryById(id: string): Promise<InventoryEntryWithMetadata | null> {
  const entry = await db.select()
    .from(inventoryTable)
    .where(eq(inventoryTable.id, id))
    .limit(1);

  if (!entry[0]) {
    return null;
  }

  return mapToInventoryEntry(entry[0]);
}

/**
 * Получить все записи из таблицы inventory_pending со статусом pending или to_delete
 */
export async function getInventoryPendingEntries(): Promise<InventoryPendingEntry[]> {
  const entries = await db.select({
    id: inventoryPendingTable.id,
    name: inventoryPendingTable.name,
    category: inventoryPendingTable.category,
    quantity: inventoryPendingTable.quantity,
    unit: inventoryPendingTable.unit,
    location: inventoryPendingTable.location,
    comment: inventoryPendingTable.comment,
    weightPerUnit: inventoryPendingTable.weightPerUnit,
    manufacturer: inventoryPendingTable.manufacturer,
    model: inventoryPendingTable.model,
    serialNumber: inventoryPendingTable.serialNumber,
    orderingLink: inventoryPendingTable.orderingLink,
    articleNumber: inventoryPendingTable.articleNumber,
    status: inventoryPendingTable.status,
    targetId: inventoryPendingTable.targetId,
    createdBy: inventoryPendingTable.createdBy,
    reviewedBy: inventoryPendingTable.reviewedBy,
    reviewedAt: inventoryPendingTable.reviewedAt,
    rejectionReason: inventoryPendingTable.rejectionReason,
    createdAt: inventoryPendingTable.createdAt,
    updatedAt: inventoryPendingTable.updatedAt,
    creatorName: employees.fullName,
  })
    .from(inventoryPendingTable)
    .leftJoin(employees, eq(inventoryPendingTable.createdBy, employees.id))
    .where(or(
      eq(inventoryPendingTable.status, 'pending'),
      eq(inventoryPendingTable.status, 'to_delete')
    ))
    .orderBy(inventoryPendingTable.createdAt);

  return entries.map(mapToInventoryPendingEntry);
}

/**
 * Получить запись на согласовании по ID
 */
export async function getInventoryPendingEntryById(id: string): Promise<InventoryPendingEntry | null> {
  const entry = await db.select({
    id: inventoryPendingTable.id,
    name: inventoryPendingTable.name,
    category: inventoryPendingTable.category,
    quantity: inventoryPendingTable.quantity,
    unit: inventoryPendingTable.unit,
    location: inventoryPendingTable.location,
    comment: inventoryPendingTable.comment,
    weightPerUnit: inventoryPendingTable.weightPerUnit,
    manufacturer: inventoryPendingTable.manufacturer,
    model: inventoryPendingTable.model,
    serialNumber: inventoryPendingTable.serialNumber,
    orderingLink: inventoryPendingTable.orderingLink,
    articleNumber: inventoryPendingTable.articleNumber,
    status: inventoryPendingTable.status,
    targetId: inventoryPendingTable.targetId,
    createdBy: inventoryPendingTable.createdBy,
    reviewedBy: inventoryPendingTable.reviewedBy,
    reviewedAt: inventoryPendingTable.reviewedAt,
    rejectionReason: inventoryPendingTable.rejectionReason,
    createdAt: inventoryPendingTable.createdAt,
    updatedAt: inventoryPendingTable.updatedAt,
    creatorName: employees.fullName,
  })
    .from(inventoryPendingTable)
    .leftJoin(employees, eq(inventoryPendingTable.createdBy, employees.id))
    .where(eq(inventoryPendingTable.id, id))
    .limit(1);

  return entry[0] ? mapToInventoryPendingEntry(entry[0]) : null;
}

/**
 * Approve inventory entry (Admin only)
 */
export async function approveInventoryEntry(id: string, reviewerId: string): Promise<InventoryEntryWithMetadata> {
  const result = await executeApproveInventory(id, reviewerId);
  return mapToInventoryEntryWithMetadataStrict(result);
}

/**
 * Reject inventory entry (Admin or Manager - Manager can only reject their own)
 */
export async function rejectInventoryEntry(id: string, reason: string, reviewerId: string, isAdmin: boolean): Promise<void> {
  // 1. Find the pending entry (check both ID and TargetID)
  const pendingEntries = await db.select()
    .from(inventoryPendingTable)
    .where(or(
      eq(inventoryPendingTable.id, id),
      eq(inventoryPendingTable.targetId, id)
    ))
    .limit(1);

  if (!pendingEntries[0]) {
    throw new Error('Pending entry not found');
  }

  const entry = pendingEntries[0];

  // 2. Permission Check
  // Admin can reject anything. Manager can only reject their own.
  if (!isAdmin && entry.createdBy !== reviewerId) {
    throw new Error('У вас нет прав на отмену этого запроса');
  }

  // 3. Execute
  await executeRejectInventory(id, reason, reviewerId);
}

// Service object for routes
export const inventoryService = {
  createInventoryEntry,
  updateInventoryEntry,
  deleteInventoryEntry,
  getAllInventoryEntries,
  getInventoryEntryById,
  getInventoryPendingEntries,
  approveInventoryEntry,
  rejectInventoryEntry,
};
