// src/server/features/personnel/lib/lifecycle.service.ts
// Логика жизненного цикла сотрудников (найм, увольнение, восстановление)

import { db } from '@serverShared/db/client';
import { employees } from '../db/employees.table';
import { employmentPeriods } from '../db/employment_periods.table';
import { notes } from '@features/notes/db/notes.table';
import { eq, and, isNull, lt, gte, lte } from 'drizzle-orm';
import { dayjs } from '@serverShared/lib/dayjs';
import { createExtendedBaseService } from '@serverShared/db/base.service';
import { AppError } from '@serverShared/lib/errors';
import { USER_ROLES } from '@shared/constants/roles';
import {
  emitEmployeeFiredEvent,
  emitEmployeeRehiredEvent,
} from '../personnel.events';
import {
  closeAllPeriods,
  deletePeriodsOnOrAfterDate,
  findManagerPeriodForMerge,
  createManagerPeriod,
  openManagerPeriod,
  syncMaidPeriods,
} from './employment.service';
import type { Employee } from '../personnel.service';

// Create base service for employees
const employeesBaseService = createExtendedBaseService(employees);

/**
 * Уволить сотрудника
 */
export async function fireEmployee(
  id: string,
  firedReason: string,
  getEmployeeFn: (id: string) => Promise<Employee | null>,
  firedAt?: Date
): Promise<void> {
  const employee = await getEmployeeFn(id);
  if (!employee) {
    throw new Error('Employee not found');
  }

  // Защита GOD-аккаунта от увольнения
  if (employee.role === USER_ROLES.GOD) {
    throw new AppError('Нельзя уволить Создателя', 403, 'FORBIDDEN');
  }

  // archivedAt - UTC представление 00:00:00 MSK выбранной даты
  const archivedAt = firedAt 
    ? firedAt 
    : dayjs().tz('Europe/Moscow').startOf('day').utc().toDate();

  await db.transaction(async (tx) => {
    // Исправление коллизии: если дата увольнения раньше даты найма (createdAt), обновляем createdAt
    const employeeData = await tx
      .select()
      .from(employees)
      .where(eq(employees.id, id))
      .limit(1);
    
    if (employeeData.length > 0) {
      const emp = employeeData[0];
      const hireDate = emp.createdAt;
      
      // Если archivedAt (дата увольнения) раньше hireDate, обновляем createdAt
      if (archivedAt < hireDate) {
        await tx
          .update(employees)
          .set({
            createdAt: archivedAt,
            updatedAt: new Date(),
          })
          .where(eq(employees.id, id));
      }
    }

    // Дата увольнения в формате YYYY-MM-DD для операций с периодами
    const endDateOnly = dayjs.utc(archivedAt).tz('Europe/Moscow').format('YYYY-MM-DD');

    // Шаг 1: Удалить коллизионные периоды - периоды, которые начинаются на дату увольнения или позже
    await tx
      .delete(employmentPeriods)
      .where(
        and(
          eq(employmentPeriods.userId, id),
          gte(employmentPeriods.startDate, endDateOnly as any)
        )
      );

    // Шаг 2: Закрыть оставшиеся открытые периоды - только периоды, начавшиеся до даты увольнения
    const openPeriodsToClose = await tx
      .select()
      .from(employmentPeriods)
      .where(
        and(
          eq(employmentPeriods.userId, id),
          isNull(employmentPeriods.endDate),
          lt(employmentPeriods.startDate, endDateOnly as any)
        )
      );

    // Закрыть все открытые периоды, начавшиеся до даты увольнения
    if (openPeriodsToClose.length > 0) {
      for (const period of openPeriodsToClose) {
        await tx
          .update(employmentPeriods)
          .set({
            endDate: endDateOnly as any,
          })
          .where(eq(employmentPeriods.id, period.id));
      }
    }

    // Обновить запись сотрудника
    await tx
      .update(employees)
      .set({
        isFired: true,
        firedReason,
        archivedAt,
        updatedAt: new Date(),
      })
      .where(eq(employees.id, id));
  });

  // Эмитировать событие employee:fired
  const firedEmployee = await getEmployeeFn(id);
  if (firedEmployee) {
    await emitEmployeeFiredEvent({
      employeeId: id,
      employee: firedEmployee,
      firedReason,
      firedAt: archivedAt,
    });
  }
}

/**
 * Восстановить сотрудника (без создания заметки)
 */
export async function restoreEmployee(id: string, returnReason?: string): Promise<void> {
  const rehireDate = dayjs().tz('Europe/Moscow').startOf('day').utc().toDate();
  const rehireDateOnly = dayjs.utc(rehireDate).tz('Europe/Moscow').format('YYYY-MM-DD');
  const now = new Date();

  await db.transaction(async (tx) => {
    // Обновить запись сотрудника
    await tx
      .update(employees)
      .set({
        isFired: false,
        firedReason: returnReason || null,
        archivedAt: null,
        createdAt: rehireDate,
        updatedAt: now,
      })
      .where(eq(employees.id, id));

    // Найти последний менеджерский период (isMaid = false), который можно объединить
    const existingManagerPeriod = await findManagerPeriodForMerge(id, rehireDateOnly, tx);

    // Если найден существующий период, объединить (установить endDate в null); иначе создать новый период
    if (existingManagerPeriod) {
      await openManagerPeriod(existingManagerPeriod.id, tx);
    } else {
      await createManagerPeriod(id, rehireDateOnly, tx);
    }
  });
}

/**
 * Повторно нанять сотрудника с причиной и системной заметкой
 */
export async function rehireEmployee(
  id: string,
  returnReason: string,
  adminId: string,
  getEmployeeFn: (id: string) => Promise<Employee | null>,
  rehireDate?: string,
  maidPeriods?: Array<{ id?: string; startDate: string; endDate?: string | null }>
): Promise<void> {
  const employee = await getEmployeeFn(id);
  if (!employee) {
    throw new Error('Employee not found');
  }

  const now = new Date();
  const hireDate = rehireDate 
    ? dayjs.utc(rehireDate).tz('Europe/Moscow').startOf('day').utc().toDate()
    : dayjs().tz('Europe/Moscow').startOf('day').utc().toDate();
  const rehireDateOnly = dayjs.utc(hireDate).tz('Europe/Moscow').format('YYYY-MM-DD');

  await db.transaction(async (tx) => {
    // Шаг 1: Обновить запись сотрудника
    await tx
      .update(employees)
      .set({
        isFired: false,
        firedReason: returnReason,
        archivedAt: null,
        createdAt: hireDate,
        updatedAt: now,
      })
      .where(eq(employees.id, id));

    // Шаг 2: Найти последний менеджерский период (isMaid = false), который можно объединить
    const existingManagerPeriod = await findManagerPeriodForMerge(id, rehireDateOnly, tx);

    // Если найден существующий период, объединить (установить endDate в null); иначе создать новый период
    if (existingManagerPeriod) {
      await openManagerPeriod(existingManagerPeriod.id, tx);
    } else {
      await createManagerPeriod(id, rehireDateOnly, tx);
    }

    // Шаг 3: Обработать периоды горничной
    // Если maidPeriods не предоставлен, автоматически восстановить существующие периоды горничной
    // Иначе использовать предоставленные периоды
    if (maidPeriods && maidPeriods.length > 0) {
      // Для каждого предоставленного периода горничной проверить, есть ли существующий период для объединения
      for (const period of maidPeriods) {
        const periodStartDate = dayjs(period.startDate).format('YYYY-MM-DD');
        
        // Найти последний период горничной для объединения (startDate <= periodStartDate)
        const existingMaidPeriods = await tx
          .select()
          .from(employmentPeriods)
          .where(
            and(
              eq(employmentPeriods.userId, id),
              eq(employmentPeriods.isMaid, true),
              lte(employmentPeriods.startDate, periodStartDate as any)
            )
          )
          .orderBy(employmentPeriods.startDate)
          .limit(1);

        if (existingMaidPeriods.length > 0) {
          // Объединить: обновить startDate и endDate
          const endDate = period.endDate ? dayjs(period.endDate).format('YYYY-MM-DD') : null;
          await tx
            .update(employmentPeriods)
            .set({
              startDate: periodStartDate as any,
              endDate: endDate as any,
            })
            .where(eq(employmentPeriods.id, existingMaidPeriods[0].id));
        } else {
          // Создать новый период
          const endDate = period.endDate ? dayjs(period.endDate).format('YYYY-MM-DD') : null;
          await tx
            .insert(employmentPeriods)
            .values({
              id: period.id || crypto.randomUUID(),
              userId: id,
              startDate: periodStartDate as any,
              endDate: endDate as any,
              isMaid: true,
              createdAt: now,
            });
        }
      }
    } else {
      // Авто-восстановление: найти все периоды горничной, которые нужно реактивировать
      const allMaidPeriods = await tx
        .select()
        .from(employmentPeriods)
        .where(
          and(
            eq(employmentPeriods.userId, id),
            eq(employmentPeriods.isMaid, true)
          )
        )
        .orderBy(employmentPeriods.startDate);

      // Для каждого периода горничной проверить, нужно ли его реактивировать
      // Период нужно реактивировать, если:
      // 1. У него есть endDate (был закрыт)
      // 2. Его startDate <= rehireDate (период начался до или в дату повторного найма)
      for (const period of allMaidPeriods) {
        const periodStartDate = dayjs(period.startDate).format('YYYY-MM-DD');
        
        // Проверить, нужно ли реактивировать этот период
        if (period.endDate && !dayjs(periodStartDate).isAfter(dayjs(rehireDateOnly))) {
          // Реактивировать, установив endDate в null
          await tx
            .update(employmentPeriods)
            .set({
              endDate: null,
            })
            .where(eq(employmentPeriods.id, period.id));
        }
      }
    }
  });

  // Эмитировать событие employee:rehired
  const rehiredEmployee = await getEmployeeFn(id);
  if (rehiredEmployee) {
    await emitEmployeeRehiredEvent({
      employeeId: id,
      employee: rehiredEmployee,
      returnReason,
      rehireDate: hireDate,
    });
  }
}
