import prisma from '../../config/database';
import { AppError } from '../../shared/middleware/errorHandler';

type CreateNotificationInput = {
  userId: string;
  type: string;
  title: string;
  message: string;
  entityType?: string | null;
  entityId?: string | null;
};

type NotifyManyInput = {
  userIds: string[];
  type: string;
  title: string;
  message: string;
  entityType?: string | null;
  entityId?: string | null;
  excludeUserIds?: string[];
};

class NotificationsService {
  async listMyNotifications(userId: string, page = 1, pageSize = 20) {
    const skip = (page - 1) * pageSize;

    const [total, notifications] = await Promise.all([
      prisma.notification.count({
        where: { userId },
      }),
      prisma.notification.findMany({
        where: { userId },
        orderBy: { createdAt: 'desc' },
        skip,
        take: pageSize,
      }),
    ]);

    return {
      notifications,
      total,
      page,
      pageSize,
      totalPages: Math.ceil(total / pageSize),
    };
  }

  async getUnreadCount(userId: string) {
    const count = await prisma.notification.count({
      where: {
        userId,
        isRead: false,
      },
    });

    return { count };
  }

  async markAsRead(notificationId: string, userId: string) {
    const notification = await prisma.notification.findFirst({
      where: {
        id: notificationId,
        userId,
      },
    });

    if (!notification) {
      throw new AppError(404, 'الإشعار غير موجود - Notification not found');
    }

    return prisma.notification.update({
      where: { id: notificationId },
      data: {
        isRead: true,
        readAt: new Date(),
      },
    });
  }

  async markAllAsRead(userId: string) {
    await prisma.notification.updateMany({
      where: {
        userId,
        isRead: false,
      },
      data: {
        isRead: true,
        readAt: new Date(),
      },
    });

    return { success: true };
  }

  async create(input: CreateNotificationInput) {
    return prisma.notification.create({
      data: {
        userId: input.userId,
        type: input.type,
        title: input.title,
        message: input.message,
        entityType: input.entityType || null,
        entityId: input.entityId || null,
      },
    });
  }

  async notifyMany(input: NotifyManyInput) {
    const excluded = new Set(input.excludeUserIds || []);
    const uniqueUserIds = Array.from(
      new Set(input.userIds.filter((id) => !!id && !excluded.has(id)))
    );

    if (uniqueUserIds.length === 0) {
      return [];
    }

    const created = [];
    for (const userId of uniqueUserIds) {
      const notification = await prisma.notification.create({
        data: {
          userId,
          type: input.type,
          title: input.title,
          message: input.message,
          entityType: input.entityType || null,
          entityId: input.entityId || null,
        },
      });
      created.push(notification);
    }

    return created;
  }

  async findUserByEmployeeId(employeeId: string) {
    return prisma.user.findFirst({
      where: {
        employeeId,
        isActive: true,
      },
      select: {
        id: true,
        employeeId: true,
        email: true,
        username: true,
      },
    });
  }

  async findManagerUserByEmployeeId(employeeId: string) {
    const employee = await prisma.employee.findUnique({
      where: { id: employeeId },
      select: {
        id: true,
        reportingToId: true,
      },
    });

    if (!employee?.reportingToId) {
      return null;
    }

    return prisma.user.findFirst({
      where: {
        employeeId: employee.reportingToId,
        isActive: true,
      },
      select: {
        id: true,
        employeeId: true,
        email: true,
        username: true,
      },
    });
  }

  async notifyEmployeeUser(params: {
    employeeId: string;
    type: string;
    title: string;
    message: string;
    entityType?: string;
    entityId?: string;
    excludeUserIds?: string[];
  }) {
    const user = await this.findUserByEmployeeId(params.employeeId);
    if (!user) return null;

    return this.notifyMany({
      userIds: [user.id],
      type: params.type,
      title: params.title,
      message: params.message,
      entityType: params.entityType,
      entityId: params.entityId,
      excludeUserIds: params.excludeUserIds,
    });
  }

  async notifyManagerForEmployee(params: {
    employeeId: string;
    type: string;
    title: string;
    message: string;
    entityType?: string;
    entityId?: string;
    excludeUserIds?: string[];
  }) {
    const managerUser = await this.findManagerUserByEmployeeId(params.employeeId);
    if (!managerUser) return null;

    return this.notifyMany({
      userIds: [managerUser.id],
      type: params.type,
      title: params.title,
      message: params.message,
      entityType: params.entityType,
      entityId: params.entityId,
      excludeUserIds: params.excludeUserIds,
    });
  }

  async resolveApprovalRecipients(params: {
    resource: 'leave_requests' | 'overtime_requests' | 'loan_requests' | 'purchase_requests' | 'expense_requests';
    fallbackEmployeeId?: string;
    fallbackToManager?: boolean;
    excludeUserIds?: string[];
  }) {
    const notifyUsers = await this.findUsersWithPermission({
      module: params.resource,
      resource: 'all',
      action: 'notify',
      excludeUserIds: params.excludeUserIds,
    });

    if (notifyUsers.length > 0) {
      return notifyUsers;
    }

    const approveUsers = await this.findUsersWithPermission({
      module: params.resource,
      resource: 'all',
      action: 'approve',
      excludeUserIds: params.excludeUserIds,
    });

    if (approveUsers.length > 0) {
      return approveUsers;
    }

    if (params.fallbackToManager && params.fallbackEmployeeId) {
      const managerUser = await this.findManagerUserByEmployeeId(params.fallbackEmployeeId);
      if (managerUser) {
        return [managerUser];
      }
    }

    return [];
  }

    async notifyApprovalRecipients(params: {
    resource: 'leave_requests' | 'overtime_requests' | 'loan_requests' | 'purchase_requests' | 'expense_requests';
    fallbackEmployeeId?: string;
    fallbackToManager?: boolean;
    type: string;
    title: string;
    message: string;
    entityType?: string;
    entityId?: string;
    excludeUserIds?: string[];
  }) {
    const recipients = await this.resolveApprovalRecipients({
      resource: params.resource,
      fallbackEmployeeId: params.fallbackEmployeeId,
      fallbackToManager: params.fallbackToManager,
      excludeUserIds: params.excludeUserIds,
    });

    return this.notifyMany({
      userIds: recipients.map((u) => u.id),
      type: params.type,
      title: params.title,
      message: params.message,
      entityType: params.entityType,
      entityId: params.entityId,
      excludeUserIds: params.excludeUserIds,
    });
  }

  async findUsersWithPermission(params: {
    module: string;
    resource?: string;
    action: string;
    departmentId?: string;
    excludeUserIds?: string[];
  }) {
    const users = await prisma.user.findMany({
      where: {
        isActive: true,
        employee: {
          status: 'ACTIVE',
        },
      },
      include: {
        employee: {
          include: {
            position: {
              include: {
                permissions: true,
              },
            },
            department: true,
          },
        },
        userRoles: {
          where: {
            role: {
              isActive: true,
            },
          },
          include: {
            role: {
              include: {
                permissions: true,
              },
            },
          },
        },
      },
    });

    const resource = params.resource || '*';
    const excluded = new Set(params.excludeUserIds || []);

    const matched = users.filter((user: any) => {
      if (excluded.has(user.id)) return false;
      if (!user.employee) return false;

      if (params.departmentId && user.employee.departmentId !== params.departmentId) {
        return false;
      }

      const positionPerms = user.employee?.position?.permissions || [];
      const rolePerms = (user.userRoles || []).flatMap((ur: any) => ur.role?.permissions || []);
      const allPerms = [...positionPerms, ...rolePerms];

      return allPerms.some((perm: any) => {
        const moduleMatch = perm.module === params.module;
        const resourceMatch =
          perm.resource === resource ||
          perm.resource === '*' ||
          perm.resource === 'all';
        const actions = Array.isArray(perm.actions) ? perm.actions : [];
        const actionMatch =
          actions.includes(params.action) ||
          actions.includes('*') ||
          actions.includes('all');

        return moduleMatch && resourceMatch && actionMatch;
      });
    });

    return matched.map((u: any) => ({
      id: u.id,
      employeeId: u.employeeId,
      username: u.username,
      email: u.email,
    }));
  }

  async notifyUsersWithPermission(params: {
    module: string;
    resource?: string;
    action: string;
    departmentId?: string;
    type: string;
    title: string;
    message: string;
    entityType?: string;
    entityId?: string;
    excludeUserIds?: string[];
  }) {
    const users = await this.findUsersWithPermission({
      module: params.module,
      resource: params.resource,
      action: params.action,
      departmentId: params.departmentId,
      excludeUserIds: params.excludeUserIds,
    });

    return this.notifyMany({
      userIds: users.map((u) => u.id),
      type: params.type,
      title: params.title,
      message: params.message,
      entityType: params.entityType,
      entityId: params.entityId,
      excludeUserIds: params.excludeUserIds,
    });
  }
}

export const notificationsService = new NotificationsService();