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

import { db } from '@serverShared/db/client';
import { contractors, contractorsPending } from './db/contractors.table';
import { employees } from '../personnel/db/employees.table';
import { eq, and, or, isNull, isNotNull } from 'drizzle-orm';
import { mapToContractorEntry, mapToContractorPendingEntry, mapPendingToContractorEntryWithMetadata } from './lib/contractors.mapper';
import { createDraft, updateDraft, deleteRequest } from './lib/contractors.drafts';
import { executeCreate, executeUpdate, executeDelete, autoApproveDraft, updatePendingDraft, deletePendingDraft } from './lib/contractors.core';
import type { ContractorPendingEntry, ContractorEntryWithMetadata, CreateContractorRequest, UpdateContractorRequest } from './contractors.schema';
import type { UserInfo } from '@serverShared/lib/auth';
import { AppPermission } from '@shared/contracts/permissions';
import { hasPermission, hasAnyPermission } from '@serverShared/plugins/rbac';

// Type for contractor entry
type ContractorEntry = typeof contractors.$inferSelect;

/**
 * Создать запись в списке контрагентов
 */
export async function createContractorEntry(request: CreateContractorRequest, user: UserInfo): Promise<ContractorEntryWithMetadata> {
  const canDirectEdit = await hasPermission(user, AppPermission.CONTRACTORS_EDIT);
  const canRequestApproval = await hasPermission(user, AppPermission.CONTRACTORS_REQUEST_APPROVAL);
  const canCreate = await hasAnyPermission(user, [AppPermission.CONTRACTORS_EDIT, AppPermission.CONTRACTORS_REQUEST_APPROVAL]);

  if (!canCreate) {
    throw new Error('У вас нет прав на создание контрагентов');
  }

  const requestWithAuthor = {
    ...request,
    createdBy: request.createdBy ?? user.id,
  };

  // Прямое создание в таблице contractors
  if (canDirectEdit) {
    return executeCreate(requestWithAuthor);
  }

  if (!canRequestApproval) {
    throw new Error('У вас нет прав на создание заявки на согласование');
  }

  // Создаем черновик на согласование
  return createDraft(requestWithAuthor);
}

/**
 * Обновить запись в списке контрагентов
 */
export async function updateContractorEntry(
  id: string,
  request: Omit<UpdateContractorRequest, 'id'>,
  user: UserInfo
): Promise<ContractorEntryWithMetadata> {
  const { name, category, contractNumber, phone, email, webLink, responsibleManagers, managerName, managerPhone, managerEmail, comment } = request;
  const canDirectEdit = await hasPermission(user, AppPermission.CONTRACTORS_EDIT);
  const canRequestApproval = await hasPermission(user, AppPermission.CONTRACTORS_REQUEST_APPROVAL);
  const canUpdate = await hasAnyPermission(user, [AppPermission.CONTRACTORS_EDIT, AppPermission.CONTRACTORS_REQUEST_APPROVAL]);

  if (!canUpdate) {
    throw new Error('У вас нет прав на изменение контрагентов');
  }

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

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

  // 2. Если нет в основной, ищем в таблице черновиков contractors_pending
  if (!existing[0]) {
    const pendingExisting = await db.select()
      .from(contractorsPending)
      .where(eq(contractorsPending.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 (phone !== undefined && phone.trim().length === 0) {
    throw new Error('Телефон обязателен');
  }

  // 4. Если это ПРЯМОЕ обновление черновика (Админом или владельцем черновика)
  if (isPendingRecord) {
    // Пользователь с EDIT: автоматическое согласование при редактировании черновика
    if (canDirectEdit) {
      const updates: any = {
        updatedAt: new Date(),
      };
      if (name !== undefined) updates.name = name.trim();
      if (category !== undefined) updates.category = category.trim();
      if (contractNumber !== undefined) updates.contractNumber = contractNumber?.trim() || null;
      if (phone !== undefined) updates.phone = phone.trim();
      if (email !== undefined) updates.email = email?.trim() || null;
      if (webLink !== undefined) updates.webLink = webLink?.trim() || null;
      if (responsibleManagers !== undefined) {
        updates.responsibleManagers = responsibleManagers.map((manager) => ({
          name: manager.name.trim(),
          phone: manager.phone?.trim() || null,
          email: manager.email?.trim() || null,
        }));
      }
      if (managerName !== undefined) updates.managerName = managerName?.trim() || null;
      if (managerPhone !== undefined) updates.managerPhone = managerPhone?.trim() || null;
      if (managerEmail !== undefined) updates.managerEmail = managerEmail?.trim() || null;
      if (comment !== undefined) updates.comment = comment?.trim() || null;

      return autoApproveDraft(id, pendingRecord!, user.id, updates);
    }

    if (!canRequestApproval) {
      throw new Error('У вас нет прав на создание заявки на согласование');
    }

    if (pendingRecord?.createdBy !== user.id) {
      throw new Error('У вас нет прав на редактирование этого черновика');
    }

    // Обычное обновление черновика (без согласования)
    const updates: any = { updatedAt: new Date() };
    if (name !== undefined) updates.name = name.trim();
    if (category !== undefined) updates.category = category.trim();
    if (contractNumber !== undefined) updates.contractNumber = contractNumber?.trim() || null;
    if (phone !== undefined) updates.phone = phone.trim();
    if (email !== undefined) updates.email = email?.trim() || null;
    if (webLink !== undefined) updates.webLink = webLink?.trim() || null;
    if (responsibleManagers !== undefined) {
      updates.responsibleManagers = responsibleManagers.map((manager) => ({
        name: manager.name.trim(),
        phone: manager.phone?.trim() || null,
        email: manager.email?.trim() || null,
      }));
    }
    if (managerName !== undefined) updates.managerName = managerName?.trim() || null;
    if (managerPhone !== undefined) updates.managerPhone = managerPhone?.trim() || null;
    if (managerEmail !== undefined) updates.managerEmail = managerEmail?.trim() || null;
    if (comment !== undefined) updates.comment = comment?.trim() || null;

    return updatePendingDraft(id, updates);
  }

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

  // Прямое обновление в таблице contractors
  if (canDirectEdit) {
    return executeUpdate(id, request);
  }

  if (!canRequestApproval) {
    throw new Error('У вас нет прав на создание заявки на согласование');
  }

  // Создаем черновик на согласование
  return updateDraft({ id, ...request, updatedBy: user.id }, existing[0]);
}

/**
 * Удалить запись из списка контрагентов (soft delete)
 */
export async function deleteContractorEntry(id: string, user: UserInfo): Promise<void> {
  const canDirectEdit = await hasPermission(user, AppPermission.CONTRACTORS_EDIT);
  const canRequestApproval = await hasPermission(user, AppPermission.CONTRACTORS_REQUEST_APPROVAL);
  const canDelete = await hasAnyPermission(user, [AppPermission.CONTRACTORS_EDIT, AppPermission.CONTRACTORS_REQUEST_APPROVAL]);

  if (!canDelete) {
    throw new Error('У вас нет прав на удаление контрагентов');
  }

  // 1. Сначала ищем в основной таблице contractors
  let existing = await db.select()
    .from(contractors)
    .where(eq(contractors.id, id))
    .limit(1);

  let isPendingRecord = false;

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

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

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

  // 4. Если это удаление черновика (еще не одобренной записи)
  if (isPendingRecord) {
    if (!canDirectEdit && (existing[0] as any).createdBy !== user.id) {
      throw new Error('У вас нет прав на удаление этого черновика');
    }

    return deletePendingDraft(id);
  }

  // 5. Прямое удаление записи из таблицы contractors
  if (canDirectEdit) {
    return executeDelete(id);
  }

  if (!canRequestApproval) {
    throw new Error('У вас нет прав на создание заявки на удаление');
  }

  // 6. Создаем запрос на удаление
  return deleteRequest(id, existing[0], user.id);
}

/**
 * Получить все записи контрагентов
 * @param archived - если false, возвращает только активные записи; если true, возвращает только архивные; если undefined, возвращает только активные записи
 * @param status - 'all' (все записи), 'pending' (только на согласовании), 'approved' (только одобренные), undefined (только одобренные)
 */
export async function getAllContractorsEntries(archived?: boolean, status?: 'all' | 'pending' | 'approved'): Promise<ContractorEntryWithMetadata[]> {
  // Если запрашиваем только pending записи - используем отдельную функцию
  if (status === 'pending') {
    const pendingEntries = await getContractorsPendingEntries();
    // Преобразуем ContractorPendingEntry в ContractorEntryWithMetadata
    return pendingEntries.map(entry => ({
      id: entry.id,
      name: entry.name,
      category: entry.category,
      contractNumber: entry.contractNumber,
      phone: entry.phone,
      email: entry.email,
      webLink: entry.webLink,
      responsibleManagers: entry.responsibleManagers as any[] || [],
      managerName: entry.managerName,
      managerPhone: entry.managerPhone,
      managerEmail: entry.managerEmail,
      comment: entry.comment,
      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(contractors.archivedAt) // archivedAt IS NOT NULL
    : isNull(contractors.archivedAt); // archivedAt IS NULL (по умолчанию)

  // Формируем условие для фильтрации по isApproved
  const approvedCondition = eq(contractors.isApproved, true);

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

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

  // Если запрашиваем все записи (all), добавляем pending записи
  if (status === 'all') {
    const pendingEntries = await getContractorsPendingEntries();
    const mappedPendingEntries = pendingEntries.map(entry => ({
      id: entry.id,
      name: entry.name,
      category: entry.category,
      contractNumber: entry.contractNumber,
      phone: entry.phone,
      email: entry.email,
      webLink: entry.webLink,
      responsibleManagers: entry.responsibleManagers as any[] || [],
      managerName: entry.managerName,
      managerPhone: entry.managerPhone,
      managerEmail: entry.managerEmail,
      comment: entry.comment,
      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(mapToContractorEntry)]
      .sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
  }

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

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

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

  return mapToContractorEntry(entry[0]);
}

/**
 * Получить все записи из таблицы contractors_pending со статусом pending или to_delete
 */
export async function getContractorsPendingEntries(): Promise<ContractorPendingEntry[]> {
  const entries = await db.select({
    id: contractorsPending.id,
    name: contractorsPending.name,
    category: contractorsPending.category,
    contractNumber: contractorsPending.contractNumber,
    phone: contractorsPending.phone,
    email: contractorsPending.email,
    webLink: contractorsPending.webLink,
    responsibleManagers: contractorsPending.responsibleManagers,
    managerName: contractorsPending.managerName,
    managerPhone: contractorsPending.managerPhone,
    managerEmail: contractorsPending.managerEmail,
    comment: contractorsPending.comment,
    status: contractorsPending.status,
    targetId: contractorsPending.targetId,
    createdBy: contractorsPending.createdBy,
    reviewedBy: contractorsPending.reviewedBy,
    reviewedAt: contractorsPending.reviewedAt,
    rejectionReason: contractorsPending.rejectionReason,
    createdAt: contractorsPending.createdAt,
    updatedAt: contractorsPending.updatedAt,
    creatorName: employees.fullName,
  })
    .from(contractorsPending)
    .leftJoin(employees, eq(contractorsPending.createdBy, employees.id))
    .where(or(
      eq(contractorsPending.status, 'pending'),
      eq(contractorsPending.status, 'to_delete')
    ))
    .orderBy(contractorsPending.createdAt);

  return entries.map(mapToContractorPendingEntry);
}
