// src/server/features/approval/approval.service.ts
// Business logic for approval system

import { db } from '../../shared/db/client';
import { approvalRequests } from './db/approval_requests.table';
import { shifts } from '../../features/schedule/db/shifts.table';
import { employees as employeesTable } from '../../shared/db/schema';
import { and, eq, isNull, or } from 'drizzle-orm';
import { loggerService } from '../../shared/lib/logger';
import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc.js';
import timezone from 'dayjs/plugin/timezone.js';
import { notify, notifyMultipleUsers } from '../notifications/notifications.service';
import { getEmployees } from '../personnel/personnel.service';
import { USER_ROLES } from '@shared/constants/roles';

// Initialize logger for approval operations (DB channel for database operations)
const approvalLogger = loggerService.get('DB').child({ module: 'approval' });

dayjs.extend(utc);
dayjs.extend(timezone);
dayjs.tz.setDefault('Europe/Moscow');

/**
 * Создать заявку на согласование изменений
 */
export async function createApprovalRequest({ entityType, targetId, requesterId, payload }: {
  entityType: 'shift' | 'employee' | 'blacklist' | 'contractor',
  targetId: string | null,
  requesterId: string,
  payload: Record<string, any>,
}) {
  try {
    const result = await db.insert(approvalRequests)
      .values({
        entityType,
        targetId,
        requesterId,
        payload,
        status: 'pending',
        createdAt: new Date(),
        updatedAt: new Date(),
      })
      .$returningId();

    const requestId = result[0].id; // $returningId() returns array of { id: string }

    approvalLogger.info(`[Approval] Request created: ${entityType} - ${targetId || 'new'} by ${requesterId}`);

    // Notify all ADMIN users about the new approval request
    const employees = await getEmployees(requesterId);
    const adminIds = employees
      .filter(emp => emp.role === USER_ROLES.ADMIN && !emp.isFired)
      .map(emp => emp.id);

    approvalLogger.debug(`[Approval Debug] Admin IDs to notify: ${adminIds.join(', ')}`);

    // Skip notifications for blacklist and contractor entities - they use direct socket notifications
    if (adminIds.length > 0 && entityType !== 'blacklist' && entityType !== 'contractor') {
      // Get month and year from payload if available
      const month = payload.month || dayjs().month() + 1;
      const year = payload.year || dayjs().year();

      await notifyMultipleUsers({
        userIds: adminIds,
        event: 'approval:created',
        data: {
          requestId,
          entityType,
          targetId: targetId || requestId, // Передаем ID сущности или запроса
          month, // Добавляем month в корень data для группировки
          year, // Добавляем year в корень data для группировки
          link: { path: '/dashboard/schedule', query: { month, year } },
        },
        targetId: targetId || requestId // ТЕПЕРЬ ПЕРЕДАЕМ И В КОРЕНЬ ОПЦИЙ
      });

      approvalLogger.info(`[Approval] Notified ${adminIds.length} admin users about new request`);
    }

    return requestId;
  } catch (error) {
    approvalLogger.error('[Approval] Failed to create approval request', { error });
    throw error;
  }
}

/**
 * Получить все заявки на согласование
 */
export async function getApprovalRequests() {
  try {
    const requests = await db.select()
      .from(approvalRequests)
      .where(isNull(approvalRequests.archivedAt))
      .orderBy(approvalRequests.createdAt);
    
    return requests;
  } catch (error) {
    approvalLogger.error('[Approval] Failed to get approval requests', { error });
    throw error;
  }
}

/**
 * Получить заявки по статусу
 */
export async function getApprovalRequestsByStatus(status: 'pending' | 'approved' | 'rejected') {
  try {
    const requests = await db.select()
      .from(approvalRequests)
      .where(
        and(
          eq(approvalRequests.status, status),
          isNull(approvalRequests.archivedAt)
        )
      )
      .orderBy(approvalRequests.createdAt);
    
    return requests;
  } catch (error) {
    approvalLogger.error('[Approval] Failed to get approval requests by status', { error });
    throw error;
  }
}

/**
 * Получить заявку по ID
 */
export async function getApprovalRequestById(id: string) {
  try {
    const request = await db.select()
      .from(approvalRequests)
      .where(eq(approvalRequests.id, id))
      .limit(1);
    
    return request[0];
  } catch (error) {
    approvalLogger.error('[Approval] Failed to get approval request by ID', { error });
    throw error;
  }
}

/**
 * Одобрить заявку на согласование
 */
export async function approveApprovalRequest(id: string) {
  try {
    // Получаем заявку
    const request = await getApprovalRequestById(id);
    if (!request) {
      throw new Error('Approval request not found');
    }
    
    // Применяем изменения к целевой таблице
    const { entityType, targetId, payload, requesterId } = request;
    
    if (entityType === 'shift') {
      // Применяем изменения к смене
      if (!targetId) {
        throw new Error('Target ID is required for shift approval');
      }
      
      await db.update(shifts)
        .set({
          ...payload as any, // Применяем все поля из payload
          updatedAt: new Date(),
        })
        .where(eq(shifts.id, targetId));
      
      approvalLogger.info(`[Approval] Shift approved: ${targetId}`);
    } else if (entityType === 'employee') {
      // Применяем изменения к сотруднику
      if (!targetId) {
        throw new Error('Target ID is required for employee approval');
      }
      
      await db.update(employeesTable)
        .set({
          ...payload as any, // Применяем все поля из payload
          updatedAt: new Date(),
        })
        .where(eq(employeesTable.id, targetId));
      
      approvalLogger.info(`[Approval] Employee approved: ${targetId}`);
    } else if (entityType === 'blacklist') {
      // Blacklist approval is handled by blacklist.service
      // Just delete the approval request
      approvalLogger.info(`[Approval] Blacklist approval request: ${targetId}`);
    } else if (entityType === 'contractor') {
      // Contractor approval is handled by contractors.workflow
      const { approveContractorEntry } = await import('../reference_books/lib/contractors.workflow');
      await approveContractorEntry(targetId!);
      approvalLogger.info(`[Approval] Contractor approval request: ${targetId}`);
    }
    
    // Удаляем заявку после успешного применения
    await db.delete(approvalRequests)
      .where(eq(approvalRequests.id, id));
    
    approvalLogger.info(`[Approval] Request approved and deleted: ${id}`);
    
    // Extract month and year from payload for notifications
    const month = (payload as any).month || dayjs().month() + 1;
    const year = (payload as any).year || dayjs().year();

    // Skip notifying the requester for blacklist and contractor entities - they use direct socket notifications
    if (requesterId && entityType !== 'blacklist' && entityType !== 'contractor') {
      await notify({
        userId: requesterId,
        event: 'approval:resolved',
        data: {
          requestId: id,
          entityType,
          targetId,
          status: 'approved',
          link: entityType === 'blacklist'
            ? { path: '/dashboard/blacklist' }
            : { path: '/dashboard/schedule', query: { month, year } },
        },
      });

      approvalLogger.info(`[Approval] Notified requester ${requesterId} about approval`);
    }
    
    // Notify all ADMIN users about the updated unapproved shifts count
    const employees = await getEmployees(requesterId);
    const adminIds = employees
      .filter(emp => emp.role === USER_ROLES.ADMIN && !emp.isFired)
      .map(emp => emp.id);
    
    if (adminIds.length > 0 && entityType !== 'blacklist' && entityType !== 'contractor') {
      await notifyMultipleUsers({
        userIds: adminIds,
        event: 'approval:created',
        data: {
          month,
          year,
          link: { path: '/dashboard/schedule', query: { month, year } },
        },
        targetId: `group_${month}_${year}`, // Pass explicit targetId matching the notification ID
      });
      
      approvalLogger.info(`[Approval] Notified ${adminIds.length} admin users about updated unapproved shifts count`);
    }
  } catch (error) {
    approvalLogger.error('[Approval] Failed to approve request', { error });
    throw error;
  }
}

/**
 * Отклонить заявку на согласование
 */
export async function rejectApprovalRequest(id: string) {
  try {
    // Получаем заявку
    const request = await getApprovalRequestById(id);
    if (!request) {
      throw new Error('Approval request not found');
    }
    
    const { entityType, targetId, payload, requesterId } = request;
    const month = (payload as any).month || dayjs().month() + 1;
    const year = (payload as any).year || dayjs().year();
    
    // Notify all ADMIN users about the updated unapproved shifts count BEFORE deleting the request
    const employees = await getEmployees(requesterId);
    const adminIds = employees
      .filter(emp => emp.role === USER_ROLES.ADMIN && !emp.isFired)
      .map(emp => emp.id);

    if (adminIds.length > 0 && entityType !== 'blacklist' && entityType !== 'contractor') {
      await notifyMultipleUsers({
        userIds: adminIds,
        event: 'approval:created',
        data: {
          month,
          year,
        },
        targetId: `group_${month}_${year}`, // Pass explicit targetId matching the notification ID
      });

      approvalLogger.info(`[Approval] Notified ${adminIds.length} admin users about updated unapproved shifts count`);
    }

    // Handle contractor rejection
    if (entityType === 'contractor') {
      const { rejectContractorEntry } = await import('../reference_books/lib/contractors.workflow');
      await rejectContractorEntry(targetId!);
      approvalLogger.info(`[Approval] Contractor rejection request: ${targetId}`);
    }

    // Удаляем заявку без применения изменений
    await db.delete(approvalRequests)
      .where(eq(approvalRequests.id, id));
    
    approvalLogger.info(`[Approval] Request rejected: ${id}`);

    // Skip notifying the requester for blacklist and contractor entities - they use direct socket notifications
    if (requesterId && entityType !== 'blacklist' && entityType !== 'contractor') {
      await notify({
        userId: requesterId,
        event: 'approval:resolved',
        data: {
          requestId: id,
          entityType,
          targetId,
          status: 'rejected',
          link: entityType === 'blacklist'
            ? { path: '/dashboard/blacklist' }
            : { path: '/dashboard/schedule', query: { month, year } },
        },
      });

      approvalLogger.info(`[Approval] Notified requester ${requesterId} about rejection`);
    }
  } catch (error) {
    approvalLogger.error('[Approval] Failed to reject request', { error });
    throw error;
  }
}

/**
 * Редактировать заявку
 */
export async function updateApprovalRequest(id: string, payload: Record<string, any>) {
  try {
    await db.update(approvalRequests)
      .set({
        payload,
        updatedAt: new Date(),
      })
      .where(eq(approvalRequests.id, id));
    
    approvalLogger.info(`[Approval] Request updated: ${id}`);
    return true;
  } catch (error) {
    approvalLogger.error('[Approval] Failed to update approval request', { error });
    throw error;
  }
}

/**
 * Архивировать заявку
 */
export async function archiveApprovalRequest(id: string) {
  try {
    await db.update(approvalRequests)
      .set({
        archivedAt: new Date(),
      })
      .where(eq(approvalRequests.id, id));
    
    approvalLogger.info(`[Approval] Request archived: ${id}`);
    return true;
  } catch (error) {
    approvalLogger.error('[Approval] Failed to archive approval request', { error });
    throw error;
  }
}
