// src/server/features/personnel/lib/employment.service.ts
// Логика управления трудовыми периодами сотрудников

import { db } from '../../../shared/db/client';
import { employmentPeriods } from '../db/employment_periods.table';
import { employees } from '../db/employees.table';
import { eq, and, isNull, lte, gte, lt, inArray, desc, or } from 'drizzle-orm';
import { dayjs } from '../../../shared/lib/dayjs';
import type { MaidPeriod, EmploymentPeriod, ScheduleUser } from '@shared/contracts/personnel';

/**
 * Проверить, есть ли у сотрудника активный период горничной сегодня
 */
export function hasActiveMaidPeriod(maidPeriods: MaidPeriod[]): boolean {
  const today = dayjs().format('YYYY-MM-DD');
  return maidPeriods.some(period => {
    const startDate = dayjs(period.startDate).format('YYYY-MM-DD');
    const endDate = period.endDate ? dayjs(period.endDate).format('YYYY-MM-DD') : null;
    return startDate <= today && (!endDate || endDate >= today);
  });
}

/**
 * Валидировать периоды горничной на отсутствие пересечений
 */
export function validateMaidPeriods(periods: Array<{ startDate: string; endDate?: string | null }>): void {
  for (let i = 0; i < periods.length; i++) {
    const p1 = periods[i];
    for (let j = i + 1; j < periods.length; j++) {
      const p2 = periods[j];
      
      const p1Start = dayjs(p1.startDate);
      const p1End = p1.endDate ? dayjs(p1.endDate) : dayjs('9999-12-31');
      const p2Start = dayjs(p2.startDate);
      const p2End = p2.endDate ? dayjs(p2.endDate) : dayjs('9999-12-31');
      
      // Проверка на пересечение: периоды пересекаются если p1Start < p2End И p2Start < p1End
      if (p1Start.isBefore(p2End) && p2Start.isBefore(p1End)) {
        throw new Error('Периоды работы горничной не должны пересекаться');
      }
    }
    
    // Валидация: startDate не должен быть после endDate
    if (p1.endDate && dayjs(p1.startDate).isAfter(dayjs(p1.endDate))) {
      throw new Error('Дата начала периода не может быть позже даты окончания');
    }
  }
}

/**
 * Получить все периоды горничной для списка пользователей
 */
export async function getMaidPeriodsForUsers(userIds: string[]): Promise<Map<string, MaidPeriod[]>> {
  const periods = await db
    .select()
    .from(employmentPeriods)
    .where(
      and(
        inArray(employmentPeriods.userId, userIds),
        eq(employmentPeriods.isMaid, true)
      )
    )
    .orderBy(employmentPeriods.startDate);
  
  const maidPeriodsMap = new Map<string, MaidPeriod[]>();
  for (const period of periods) {
    if (!maidPeriodsMap.has(period.userId)) {
      maidPeriodsMap.set(period.userId, []);
    }
    maidPeriodsMap.get(period.userId)!.push({
      id: period.id,
      startDate: dayjs(period.startDate).format('YYYY-MM-DD'),
      endDate: period.endDate ? dayjs(period.endDate).format('YYYY-MM-DD') : null,
    });
  }
  
  return maidPeriodsMap;
}

/**
 * Получить все периоды горничной для одного пользователя
 */
export async function getMaidPeriodsForUser(userId: string): Promise<MaidPeriod[]> {
  const periods = await db
    .select()
    .from(employmentPeriods)
    .where(
      and(
        eq(employmentPeriods.userId, userId),
        eq(employmentPeriods.isMaid, true)
      )
    )
    .orderBy(employmentPeriods.startDate);
  
  return periods.map((period: any) => ({
    id: period.id,
    startDate: dayjs(period.startDate).format('YYYY-MM-DD'),
    endDate: period.endDate ? dayjs(period.endDate).format('YYYY-MM-DD') : null,
  }));
}

/**
 * Получить все периоды (и менеджерские, и горничные) для пользователя
 */
export async function getAllPeriodsForUser(userId: string, tx?: any): Promise<EmploymentPeriod[]> {
  const database = tx || db;
  const periods = await database
    .select()
    .from(employmentPeriods)
    .where(eq(employmentPeriods.userId, userId))
    .orderBy(employmentPeriods.startDate);
  
  return periods.map((period: any) => ({
    id: period.id,
    userId: period.userId,
    startDate: dayjs(period.startDate).format('YYYY-MM-DD'),
    endDate: period.endDate ? dayjs(period.endDate).format('YYYY-MM-DD') : null,
    isMaid: Boolean(period.isMaid),
    createdAt: period.createdAt.toISOString(),
  }));
}

/**
 * Синхронизировать периоды горничной для пользователя
 * Создает, обновляет и удаляет периоды в соответствии с переданным списком
 */
export async function syncMaidPeriods(
  userId: string,
  maidPeriods: Array<{ id?: string; startDate: string; endDate?: string | null; _deleted?: boolean }>,
  tx: any
): Promise<void> {
  // Получить существующие периоды горничной
  const existingPeriods = await tx
    .select()
    .from(employmentPeriods)
    .where(
      and(
        eq(employmentPeriods.userId, userId),
        eq(employmentPeriods.isMaid, true)
      )
    )
    .orderBy(employmentPeriods.startDate);
  
  const existingPeriodIds = new Set(existingPeriods.map((p: any) => p.id));
  const now = new Date();
  
  // Обработка периодов: удаление, обновление, вставка
  for (const period of maidPeriods) {
    // Пропустить периоды, отмеченные для удаления без ID
    if (period._deleted && !period.id) {
      continue;
    }
    
    // Удалить период, если он отмечен для удаления
    if (period._deleted && period.id) {
      await tx
        .delete(employmentPeriods)
        .where(eq(employmentPeriods.id, period.id));
      continue;
    }
    
    const startDate = dayjs(period.startDate).format('YYYY-MM-DD');
    const endDate = period.endDate ? dayjs(period.endDate).format('YYYY-MM-DD') : null;
    
    if (period.id && existingPeriodIds.has(period.id)) {
      // Обновить существующий период
      await tx
        .update(employmentPeriods)
        .set({
          startDate: startDate as any,
          endDate: endDate as any,
        })
        .where(eq(employmentPeriods.id, period.id));
    } else {
      // Вставить новый период
      await tx
        .insert(employmentPeriods)
        .values({
          id: period.id || crypto.randomUUID(),
          userId,
          startDate: startDate as any,
          endDate: endDate as any,
          isMaid: true,
          createdAt: now,
        });
    }
  }
  
  // Удалить периоды, которых нет в новом списке
  const newPeriodIds = new Set(
    maidPeriods
      .filter(p => !p._deleted && p.id)
      .map(p => p.id!)
  );
  
  for (const period of existingPeriods) {
    if (!newPeriodIds.has(period.id)) {
      await tx
        .delete(employmentPeriods)
        .where(eq(employmentPeriods.id, period.id));
    }
  }
}

/**
 * Закрыть все открытые периоды для пользователя на указанную дату
 */
export async function closeAllPeriods(userId: string, endDate: string, tx: any): Promise<void> {
  const openPeriods = await tx
    .select()
    .from(employmentPeriods)
    .where(
      and(
        eq(employmentPeriods.userId, userId),
        isNull(employmentPeriods.endDate)
      )
    );
  
  for (const period of openPeriods) {
    await tx
      .update(employmentPeriods)
      .set({
        endDate: endDate as any,
      })
      .where(eq(employmentPeriods.id, period.id));
  }
}

/**
 * Удалить периоды, которые начинаются на указанную дату или позже
 */
export async function deletePeriodsOnOrAfterDate(userId: string, startDate: string, tx: any): Promise<void> {
  await tx
    .delete(employmentPeriods)
    .where(
      and(
        eq(employmentPeriods.userId, userId),
        gte(employmentPeriods.startDate, startDate as any)
      )
    );
}

/**
 * Найти последний менеджерский период для пользователя
 */
export async function findLatestManagerPeriod(userId: string, tx?: any): Promise<EmploymentPeriod | null> {
  const database = tx || db;
  const periods = await database
    .select()
    .from(employmentPeriods)
    .where(
      and(
        eq(employmentPeriods.userId, userId),
        eq(employmentPeriods.isMaid, false)
      )
    )
    .orderBy(desc(employmentPeriods.startDate))
    .limit(1);
  
  if (periods.length === 0) {
    return null;
  }
  
  const period = periods[0];
  return {
    id: period.id,
    userId: period.userId,
    startDate: dayjs(period.startDate).format('YYYY-MM-DD'),
    endDate: period.endDate ? dayjs(period.endDate).format('YYYY-MM-DD') : null,
    isMaid: Boolean(period.isMaid),
    createdAt: period.createdAt.toISOString(),
  };
}

/**
 * Найти менеджерский период для слияния (startDate <= targetDate)
 */
export async function findManagerPeriodForMerge(userId: string, targetDate: string, tx: any): Promise<EmploymentPeriod | null> {
  const periods = await tx
    .select()
    .from(employmentPeriods)
    .where(
      and(
        eq(employmentPeriods.userId, userId),
        eq(employmentPeriods.isMaid, false),
        lte(employmentPeriods.startDate, targetDate as any)
      )
    )
    .orderBy(desc(employmentPeriods.startDate))
    .limit(1);
  
  if (periods.length === 0) {
    return null;
  }
  
  const period = periods[0];
  return {
    id: period.id,
    userId: period.userId,
    startDate: dayjs(period.startDate).format('YYYY-MM-DD'),
    endDate: period.endDate ? dayjs(period.endDate).format('YYYY-MM-DD') : null,
    isMaid: Boolean(period.isMaid),
    createdAt: period.createdAt.toISOString(),
  };
}

/**
 * Создать менеджерский период для пользователя
 */
export async function createManagerPeriod(userId: string, startDate: string, tx: any): Promise<void> {
  await tx
    .insert(employmentPeriods)
    .values({
      id: crypto.randomUUID(),
      userId,
      startDate: startDate as any,
      endDate: null,
      isMaid: false,
      createdAt: new Date(),
    });
}

/**
 * Обновить менеджерский период
 */
export async function updateManagerPeriod(periodId: string, startDate: string, tx: any): Promise<void> {
  await tx
    .update(employmentPeriods)
    .set({
      startDate: startDate as any,
    })
    .where(eq(employmentPeriods.id, periodId));
}

/**
 * Открыть менеджерский период (установить endDate в null)
 */
export async function openManagerPeriod(periodId: string, tx: any): Promise<void> {
  await tx
    .update(employmentPeriods)
    .set({
      endDate: null,
    })
    .where(eq(employmentPeriods.id, periodId));
}

/**
 * Получить пользователей для графика смен с фильтрацией по ролям на основе периодов работы
 */
export async function getUsersForScheduleGrid(month: number, year: number): Promise<{ managers: ScheduleUser[]; maids: ScheduleUser[] }> {
  const firstDayOfMonth = dayjs.utc().year(year).month(month - 1).startOf('month').toDate();
  const lastDayOfMonth = dayjs.utc().year(year).month(month - 1).endOf('month').toDate();

  // Получаем всех пользователей, у которых есть периоды работы, пересекающиеся с выбранным месяцем
  const allUsers = await db
    .selectDistinct({
      id: employees.id,
      username: employees.username,
      email: employees.email,
      role: employees.role,
      isFired: employees.isFired,
      fullName: employees.fullName,
    })
    .from(employees)
    .innerJoin(employmentPeriods, eq(employees.id, employmentPeriods.userId))
    .where(and(
      lte(employmentPeriods.startDate, lastDayOfMonth),
      or(
        gte(employmentPeriods.endDate, firstDayOfMonth),
        isNull(employmentPeriods.endDate)
      )
    ));

  // Получаем все периоды работы для этих пользователей
  const userIds = allUsers.map(u => u.id);
  const allPeriods = await db
    .select()
    .from(employmentPeriods)
    .where(inArray(employmentPeriods.userId, userIds));

  // Разделяем периоды по пользователям и ролям
  const userPeriods = new Map<string, { managerPeriods: any[]; maidPeriods: any[] }>();
  for (const period of allPeriods) {
    if (!userPeriods.has(period.userId)) {
      userPeriods.set(period.userId, { managerPeriods: [], maidPeriods: [] });
    }
    const userPeriod = userPeriods.get(period.userId)!;

    // ПРЯМАЯ ПРОВЕРКА: смотрим оба варианта ключа
    const rawValue = (period as any).isMaid !== undefined ? (period as any).isMaid : (period as any).is_maid;
    const isMaidFinal = rawValue === true || Number(rawValue) === 1 || rawValue === '1';

    const normalizedPeriod = {
      id: period.id,
      userId: period.userId,
      startDate: period.startDate instanceof Date ? period.startDate.toISOString().split('T')[0] : String(period.startDate),
      endDate: period.endDate ? (period.endDate instanceof Date ? period.endDate.toISOString().split('T')[0] : String(period.endDate)) : null,
      isMaid: isMaidFinal,
      createdAt: period.createdAt instanceof Date ? period.createdAt.toISOString() : String(period.createdAt)
    };

    if (isMaidFinal) {
      userPeriod.maidPeriods.push(normalizedPeriod);
    } else {
      userPeriod.managerPeriods.push(normalizedPeriod);
    }
  }

  // Формируем списки managers и maids на основе периодов работы
  const managers: ScheduleUser[] = [];
  const maids: ScheduleUser[] = [];

  for (const user of allUsers) {
    const periods = userPeriods.get(user.id) || { managerPeriods: [], maidPeriods: [] };
    const nameParts = (user.fullName || '').split(' ');
    const userData = {
      id: user.id,
      username: user.username || '',
      firstName: nameParts[0] || '',
      lastName: nameParts.slice(1).join(' ') || '',
      fullName: user.fullName || user.username || '',
      email: user.email,
      role: user.role as 'ADMIN' | 'MANAGER' | 'MAID',
      isMaidAlso: periods.maidPeriods.length > 0,
    };

    // Пользователь попадает в список managers, если у него есть период с isMaid: false, пересекающийся с месяцем
    const hasManagerPeriod = periods.managerPeriods.some(p => {
      const startDate = new Date(p.startDate);
      const endDate = p.endDate ? new Date(p.endDate) : null;
      return startDate <= lastDayOfMonth && (endDate === null || endDate >= firstDayOfMonth);
    });

    if (hasManagerPeriod) {
      managers.push(userData);
    }

    // Пользователь попадает в список maids, если у него есть период с isMaid: true, пересекающийся с месяцем
    const hasMaidPeriod = periods.maidPeriods.some(p => {
      const startDate = new Date(p.startDate);
      const endDate = p.endDate ? new Date(p.endDate) : null;
      return startDate <= lastDayOfMonth && (endDate === null || endDate >= firstDayOfMonth);
    });

    if (hasMaidPeriod) {
      maids.push(userData);
    }
  }

  return { managers, maids };
}
