import { HttpStatus, Injectable } from '@nestjs/common';
import {
  DatabaseService,
  ErrorResponse,
  logger,
  NotificationSourceType,
} from '../../config';
import { NotificationTargetTypeEnum } from '../../config/database/entities';
import { NotificationGateway } from '../../notification.socket';
import { createGeneralError, getDateDuration } from '../../utils';
import {
  NotificationFilterDto,
  NotificationMarkAsReadDto,
} from './notifications.dto';

type SendNotificationPayload = {
  owner_id: string;
  title: string;
  message: string;
  type: NotificationSourceType;
  meta?: any;
  start_time?: Date;
  end_time?: Date;
  duration?: number;
  reference_id?: string;
  member_id?: string;
  member_ids?: string[];
  department_ids?: string[];
  roles?: string[];
  include_owner?: boolean;
  include_admins?: boolean;
  include_all_members?: boolean;
};

@Injectable()
export class NotificationsService {
  constructor(
    private readonly db: DatabaseService,
    private readonly gateway: NotificationGateway,
  ) {}

  private buildPaginationDetails(page: number, limit: number, total: number) {
    const totalPages = Math.ceil(total / limit);

    return {
      pagination: {
        total_items: total,
        total_pages: totalPages,
        current_page: page,
        items_per_page: limit,
        has_next_page: page < totalPages,
        has_previous_page: page > 1,
        next_page: page < totalPages ? page + 1 : null,
        previous_page: page > 1 ? page - 1 : null,
      },
    };
  }

  async sendNotification(data: SendNotificationPayload) {
    try {
      // Resolve recipients
      let memberIds = await this.resolveRecipients({
        owner_id: data.owner_id,
        member_id: data.member_id,
        member_ids: data.member_ids,
        department_ids: data.department_ids,
        roles: data.roles,
        include_admins: data.include_admins,
        include_all_members: data.include_all_members,
      });

      // ✅ Include owner as receiver if needed
      if (data.include_owner && !memberIds.includes(data.owner_id)) {
        memberIds.push(data.owner_id);
      }

      const duration = data.end_time ? getDateDuration(data.end_time, data.start_time) : data.duration || 0;
        // data.duration ?? getDateDuration(data.end_time, data.start_time);

      // Create notification
      const notification = await this.db.notifications.create({
        data: {
          owner_id: data.owner_id,
          title: data.title,
          message: data.message,
          type: data.type,
          meta: data.meta,
          start_time: data?.start_time || new Date(),
          end_time: data?.end_time || new Date(),
          duration,
          source_id: data.reference_id,
        },
      });

      // Create notification targets
      const targets: any[] = [];

      if (data.member_id || data.member_ids?.length) {
        targets.push({
          notification_id: notification.id,
          target_type: NotificationTargetTypeEnum.member,
          target_value: [
            ...(data.member_id ? [data.member_id] : []),
            ...(data.member_ids ?? []),
          ],
        });
      }

      if (data.department_ids?.length) {
        targets.push({
          notification_id: notification.id,
          target_type: NotificationTargetTypeEnum.department,
          target_value: data.department_ids,
        });
      }

      if (data.roles?.length || data.include_admins) {
        targets.push({
          notification_id: notification.id,
          target_type: NotificationTargetTypeEnum.role,
          target_value: [
            ...(data.roles ?? []),
            ...(data.include_admins ? ['admin'] : []),
          ],
        });
      }

      if (data.include_all_members) {
        targets.push({
          notification_id: notification.id,
          target_type: NotificationTargetTypeEnum.all,
          target_value: null,
        });
      }

      if (targets.length) {
        await this.db.notification_targets.createMany({
          data: targets,
        });
      }

      // Create receivers
      let receivers: any[] = [];

      if (memberIds.length) {
        await this.db.notification_receivers.createMany({
          data: memberIds.map((memberId) => ({
            notification_id: notification.id,
            member_id: memberId,
            is_read: false,
            read_at: new Date(),
          })),
        });

        receivers = await this.db.notification_receivers.findMany({
          where: {
            notification_id: notification.id,
            member_id: { in: memberIds },
          },
          include: {
            notification: true,
          },
        });
      }

      // Emit via gateway
      setImmediate(() => {
        try {
          console.log('📤 Emitting notification', {
            memberIds,
          });

          receivers.forEach((receiver) => {
            if (receiver?.member_id === data.owner_id) {
              this.gateway.sendToOwner(data.owner_id, receiver);
            } else {
              this.gateway.sendToUsers([receiver.member_id], receiver);
            }
          });
        } catch (err) {
          logger.error('Socket emit failed', err);
        }
      });

      return notification;
    } catch (err) {
      logger.error('Notification failed', err);
      console.log('Notification failed', err);
      return null;
    }
  }

  async getNotifications(query: NotificationFilterDto, member_id: string) {
    try {
      const page = query?.page || 1;
      const limit = query.limit || 20;

      const where: any = {
        member_id: member_id,
        notification: {},
      };

      if (query.start_date || query.end_date) {
        where.notification.created_at = {};

        if (query.start_date) {
          where.notification.created_at.gte = query.start_date;
        }

        if (query.end_date) {
          where.notification.created_at.lte = query.end_date;
        }
      }

      const [rows, total] = await Promise.all([
        this.db.notification_receivers.findMany({
          where,
          include: {
            notification: true,
          },
          orderBy: {
            notification: {
              created_at: 'desc',
            },
          },
          skip: (page - 1) * limit,
          take: limit,
        }),

        this.db.notification_receivers.count({ where }),
      ]);

      const receivers = await this.db.notification_receivers.findMany({
        where: { member_id },
        include: {
          notification: true,
        },
      });

      const summary = receivers.reduce(
        (acc, item) => {
          const type = item.notification.type;

          acc.total_notification += 1;

          if (!item.is_read) acc.unread_notification += 1;

          switch (type) {
            case 'HOLIDAY':
              acc.holiday_notification++;
              break;
            case 'LEAVE':
              acc.leave_notification++;
              break;
            case 'NOTICE':
              acc.notice_notification++;
              break;
            case 'ATTENDANCE':
              acc.attendance_notification++;
              break;
          }

          return acc;
        },
        {
          total_notification: 0,
          unread_notification: 0,
          holiday_notification: 0,
          leave_notification: 0,
          notice_notification: 0,
          attendance_notification: 0,
        },
      );

      return {
        data: rows,
        details: {
          ...this.buildPaginationDetails(page, limit, total),
          summary,
        },
        message: 'Notifications retrieved successfully',
        status: HttpStatus.OK,
      };
    } catch (error) {
      logger.error(`Error in getNotifications: ${String(error)}`);

      if (error instanceof ErrorResponse) throw error;

      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async markAsRead(data: NotificationMarkAsReadDto) {
    try {
      const { notification_id, member_id } = data || {};
      const notificationReceiver =
        await this.db.notification_receivers.findUnique({
          where: {
            id: notification_id,
            member_id: member_id,
          },
        });

      if (!notificationReceiver) {
        throw createGeneralError(
          'Notification not found',
          'notificationReceiverId',
          HttpStatus.NOT_FOUND,
        );
      }

      await this.db.notification_receivers.update({
        where: {
          id: notification_id,
        },
        data: {
          is_read: true,
          read_at: new Date(),
        },
      });

      return {
        data: null,
        status: HttpStatus.OK,
        message: 'Notification marked as read',
        details: null,
      };
    } catch (error) {
      logger.error(`Error in getLeaves: ${String(error)}`);
      if (error instanceof ErrorResponse) throw error;
      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  async markAllAsRead(memberId: string) {
    try {
      await this.db.notification_receivers.updateMany({
        where: {
          member_id: memberId,
          is_read: false,
        },
        data: {
          is_read: true,
          read_at: new Date(),
        },
      });

      console.log(`All notifications marked as read for member ${memberId}`);

      return {
        data: null,
        status: HttpStatus.OK,
        message: 'All notifications marked as read',
        details: null,
      };
    } catch (error) {
      logger.error(`Error in markAllAsRead: ${String(error)}`);

      if (error instanceof ErrorResponse) throw error;

      throw createGeneralError(String(error), 'root', HttpStatus.BAD_REQUEST);
    }
  }

  // This method resolves the final list of recipient member IDs based on the various targeting criteria provided in the notification payload
  private async resolveRecipients(params: {
    owner_id: string;
    member_id?: string;
    member_ids?: string[];
    department_ids?: string[];
    roles?: string[];
    include_admins?: boolean;
    include_all_members?: boolean;
  }) {
    const recipients = new Set<string>();

    // Direct member targeting is straightforward - we just add the specified member IDs to the recipients set
    if (params.member_id) {
      recipients.add(params.member_id);
    }

    // If multiple member IDs are provided, we add all of them to the recipients set
    if (params.member_ids?.length) {
      params.member_ids.forEach((id) => {
        recipients.add(id);
      });
    }

    // For department and role targeting, we need to query the database to find all active members that match the criteria and add their member IDs to the recipients set
    if (params.department_ids?.length) {
      const departmentMembers = await this.db.members.findMany({
        where: {
          owner_id: params.owner_id,
          department_id: {
            in: params.department_ids,
          },
          status: 'active',
        },
        select: {
          id: true,
        },
      });

      // We add each member ID from the query result to the recipients set
      departmentMembers.forEach((member) => {
        recipients.add(member.id);
      });
    }

    // Similarly, for role targeting, we query for members that have any of the specified roles and are active, then add their IDs to the recipients set
    if (params.roles?.length) {
      const roleMembers = await this.db.members.findMany({
        where: {
          owner_id: params.owner_id,
          role: {
            in: params.roles,
          },
          status: 'active',
        },
        select: {
          id: true,
        },
      });

      // We add each member ID from the query result to the recipients set
      roleMembers.forEach((member) => {
        recipients.add(member.id);
      });
    }

    // If include_admins is true, we query for all active members with the 'admin' role and add their IDs to the recipients set
    if (params.include_admins) {
      const admins = await this.db.members.findMany({
        where: {
          owner_id: params.owner_id,
          role: 'admin',
          status: 'active',
        },

        select: {
          id: true,
        },
      });

      admins.forEach((admin) => {
        recipients.add(admin.id);
      });
    }

    // If include_all_members is true, we query for all active members of the owner and add their IDs to the recipients set
    if (params.include_all_members) {
      const members = await this.db.members.findMany({
        where: {
          owner_id: params.owner_id,
          status: 'active',
        },
        select: {
          id: true,
        },
      });
      members.forEach((member) => {
        recipients.add(member.id);
      });
    }

    return Array.from(recipients);
  }
}
