import { Injectable, HttpException, HttpStatus } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model, Types } from 'mongoose';
import { Notification, NotificationDocument } from './schema/notification.schema';
import { ModelsService } from 'src/models/models.service';
import { CommonService } from 'src/common/common.service';
import { EmailService } from 'src/common/services/email.service';
import { CreateBroadcastDto, GetBroadcastListDto } from './dto/broadcast.dto';
import { Broadcast, BroadcastStatus, BroadcastType, BroadcastTarget } from './schema/broadcast.schema';
import { NotificationType } from './enums/notification-type.enum';
import { I18nType } from 'src/common/enums/i18n-type.enum';


@Injectable()
export class NotificationService {
  constructor(

    private readonly Model: ModelsService,
    private readonly CommonService: CommonService,
    private readonly EmailService: EmailService,

) {}

  async getNotificationsByUserId( page: number = 1, limit: number = 20, req : any ) {

    const userObjectId = new Types.ObjectId(req.user_data._id);
    const languageCode = this.CommonService.getUserLanguage(req)

    const skip = (page - 1) * limit;
    
    const [notifications, total, unreadCount] = await Promise.all([
      this.Model.notifications
        .find({ sent_to: userObjectId })
        .sort({ created_at: -1 })
        .skip(skip)
        .limit(limit)
        .lean(),
      this.Model.notifications.countDocuments({ sent_to: userObjectId }),
      this.Model.notifications.countDocuments({ sent_to: userObjectId, is_read: false })
    ]);
    
    // Translate notification keys to actual text
    const translatedNotifications = this.CommonService.processNotificationsWithTranslation(
      notifications,
      languageCode
    );
    
    const response = this.CommonService.paginatedResponse(
      'Notifications retrieved successfully',
      translatedNotifications,
      total,
      page,
      limit
    );
    
    // Add unread count to response
    return {
      ...response,
      unread_count: unreadCount
    };
  }

  async markAllAsRead(userId: string) {
    const userObjectId = new Types.ObjectId(userId);
    
    await this.Model.notifications.updateMany(
      { sent_to: userObjectId, is_read: false },
      { $set: { is_read: true, updated_at: +new Date() } }
    );
    
    return this.CommonService.successResponse('All notifications marked as read');
  }

  async getUnreadCount(userId: string) {
    const userObjectId = new Types.ObjectId(userId);
    
    const count = await this.Model.notifications.countDocuments({
      sent_to: userObjectId,
      is_read: false
    });
    
    return this.CommonService.successResponse('Unread count retrieved', { count });
  }

  async createBroadcast(dto: CreateBroadcastDto, adminId: string) {
    try {
      const { type, target, title, message, email_subject, target_users } = dto;

      // Validate email_subject for EMAIL or BOTH type
      if ((type === BroadcastType.EMAIL || type === BroadcastType.BOTH) && !email_subject) {
        throw new HttpException(
          { message: 'Email subject is required for EMAIL or BOTH broadcast type' },
          HttpStatus.BAD_REQUEST
        );
      }

      // Validate target_users for SPECIFIC_USERS target
      if (target === BroadcastTarget.SPECIFIC_USERS && (!target_users || target_users.length === 0)) {
        throw new HttpException(
          { message: 'Target users are required when target is SPECIFIC_USERS' },
          HttpStatus.BAD_REQUEST
        );
      }

      // Get user IDs to send broadcast to
      let userIds: Types.ObjectId[] = [];
      
      if (target === BroadcastTarget.ALL) {
        // Get all regular users (exclude admins)
        const allUsers: any[] = await this.Model.UserModel.find({
          user_type: { $in: ['USER', 'VENDOR'] },
          is_profile_complete: true
        }).select('_id').lean();
        
        userIds = allUsers.map((user) => new Types.ObjectId(user._id));
      } else if (target === BroadcastTarget.ONLY_USERS) {
        // Get only regular users (exclude vendors and admins)
        const onlyUsers: any[] = await this.Model.UserModel.find({
          user_type: 'USER',
          is_profile_complete: true
        }).select('_id').lean();
        
        userIds = onlyUsers.map((user) => new Types.ObjectId(user._id));
      } else if (target === BroadcastTarget.ONLY_VENDORS) {
        // Get only vendors (exclude regular users and admins)
        const onlyVendors: any[] = await this.Model.UserModel.find({
          user_type: 'VENDOR',
          is_profile_complete: true
        }).select('_id').lean();
        
        userIds = onlyVendors.map((user) => new Types.ObjectId(user._id));
      } else {
        // Validate specific user IDs
        userIds = target_users.map(id => new Types.ObjectId(id));
        
        // Check if users exist
        const validUsers = await this.Model.UserModel.find({
          _id: { $in: userIds }
        }).select('_id').lean();
        
        if (validUsers.length !== userIds.length) {
          throw new HttpException(
            { message: 'Some user IDs are invalid' },
            HttpStatus.BAD_REQUEST
          );
        }
      }

      // Create broadcast record
      const broadcast: any = await this.Model.BroadcastModel.create({
        type,
        target,
        title,
        message,
        email_subject: email_subject || null,
        target_users: userIds,
        created_by: new Types.ObjectId(adminId),
        status: BroadcastStatus.PENDING,
        total_recipients: userIds.length,
        pending_count: userIds.length,
        created_at: +new Date(),
        updated_at: +new Date()
      });

      // Process broadcast asynchronously
      const broadcastId = (broadcast as any)._id.toString();
      this.processBroadcast(broadcastId).catch(error => {
        console.error('Broadcast processing failed:', error);
      });

      return this.CommonService.successResponse('Broadcast created and processing started', {
        broadcast_id: broadcast._id,
        total_recipients: userIds.length
      });
    } catch (error) {
      throw error;
    }
  }

  async processBroadcast(broadcastId: string) {
    try {
      const broadcast: any = await this.Model.BroadcastModel.findById(broadcastId);
      
      if (!broadcast) {
        throw new Error('Broadcast not found');
      }

      // Update status to processing
      await this.Model.BroadcastModel.updateOne(
        { _id: broadcast._id },
        { 
          $set: { 
            status: BroadcastStatus.PROCESSING,
            started_at: +new Date(),
            updated_at: +new Date()
          }
        }
      );

      let successCount = 0;
      let failedCount = 0;
      const errors: string[] = [];

      // Process in batches of 50 to avoid overwhelming the system
      const batchSize = 50;
      const userIds = broadcast.target_users;

      for (let i = 0; i < userIds.length; i += batchSize) {
        const batch = userIds.slice(i, i + batchSize);
        
        // Get user details for this batch
        const users = await this.Model.UserModel.find({
          _id: { $in: batch }
        }).select('_id email name').lean();

        // Process each user in the batch
        const promises = users.map(async (user) => {
          try {
            // Send notification if type is NOTIFICATION or BOTH
            if (broadcast.type === BroadcastType.NOTIFICATION || broadcast.type === BroadcastType.BOTH) {
              await this.Model.notifications.create({
                type: 'BROADCAST',
                title: broadcast.title,
                message: broadcast.message,
                sent_by: broadcast.created_by,
                sent_to: user._id,
                created_at: +new Date(),
                updated_at: +new Date()
              });

              // Send push notification
              try {
                // Get user's session/FCM tokens
                const userSessions = await this.Model.SessionModel.find({
                  user_id: user._id,
                  fcm_token: { $exists: true, $ne: null }
                }).select('fcm_token device_type').lean();

                if (userSessions && userSessions.length > 0) {
                  const fcmTokens = userSessions
                    .map((session: any) => session.fcm_token)
                    .filter((token: string) => token);

                  if (fcmTokens.length > 0) {
                    await this.CommonService.sendPushNotification(fcmTokens, {
                      title: broadcast.title,
                      message: broadcast.message,
                      type: 'BROADCAST',
                      sent_by: broadcast.created_by,
                      sent_to: user._id
                    });
                  }
                }
              } catch (pushError) {
                console.error(`Failed to send push notification to user ${user._id}:`, pushError);
                // Don't throw - continue with other users
              }
            }

            // Send email if type is EMAIL or BOTH
            if (broadcast.type === BroadcastType.EMAIL || broadcast.type === BroadcastType.BOTH) {
              if (user.email) {
                await this.EmailService.sendBroadcastEmail(
                  user.email,
                  user.name || 'User',
                  broadcast.email_subject || broadcast.title,
                  broadcast.message
                );
              }
            }

            successCount++;
          } catch (error) {
            failedCount++;
            errors.push(`Failed for user ${user._id}: ${error.message}`);
          }
        });

        await Promise.all(promises);

        // Update progress
        const processedCount = successCount + failedCount;
        await this.Model.BroadcastModel.updateOne(
          { _id: broadcast._id },
          {
            $set: {
              success_count: successCount,
              failed_count: failedCount,
              pending_count: userIds.length - processedCount,
              updated_at: +new Date()
            }
          }
        );
      }

      // Update final status
      let finalStatus = BroadcastStatus.COMPLETED;
      if (failedCount > 0 && successCount > 0) {
        finalStatus = BroadcastStatus.PARTIALLY_FAILED;
      } else if (failedCount > 0 && successCount === 0) {
        finalStatus = BroadcastStatus.FAILED;
      }

      await this.Model.BroadcastModel.updateOne(
        { _id: broadcast._id },
        {
          $set: {
            status: finalStatus,
            success_count: successCount,
            failed_count: failedCount,
            pending_count: 0,
            error_message: errors.length > 0 ? errors.slice(0, 10).join('; ') : null,
            completed_at: +new Date(),
            updated_at: +new Date()
          }
        }
      );

      console.log(`Broadcast ${broadcastId} completed: ${successCount} success, ${failedCount} failed`);
    } catch (error) {
      console.error('Broadcast processing error:', error);
      
      // Update status to failed
      await this.Model.BroadcastModel.updateOne(
        { _id: broadcastId },
        {
          $set: {
            status: BroadcastStatus.FAILED,
            error_message: error.message,
            updated_at: +new Date()
          }
        }
      ).catch(err => console.error('Failed to update broadcast status:', err));
    }
  }

  async getBroadcastList(queryDto: GetBroadcastListDto, adminId: string) {
    try {
      const { page = 1, limit = 10, status, type } = queryDto;

      // Build query
      const query: any = {};

      if (status) {
        query.status = status;
      }

      if (type) {
        query.type = type;
      }

      // Get total count
      const total = await this.Model.BroadcastModel.countDocuments(query);

      // Get broadcast list with pagination
      const broadcasts = await this.Model.BroadcastModel.find(query)
        .sort({ created_at: -1 })
        .skip((page - 1) * limit)
        .limit(limit)
        .lean();

      return this.CommonService.paginatedResponse(
        'Broadcast list retrieved successfully',
        broadcasts,
        total,
        page,
        limit
      );
    } catch (error) {
      throw error;
    }
  }

  async getBroadcastById(broadcastId: string, adminId: string) {
    try {
      const broadcast = await this.Model.BroadcastModel.findById(broadcastId).lean();

      if (!broadcast) {
        throw new HttpException(
          { message: 'Broadcast not found' },
          HttpStatus.NOT_FOUND
        );
      }

      return this.CommonService.successResponse('Broadcast retrieved successfully', broadcast);
    } catch (error) {
      throw error;
    }
  }

  async retryFailedBroadcast(broadcastId: string, adminId: string) {
    try {
      const broadcast: any = await this.Model.BroadcastModel.findById(broadcastId);

      if (!broadcast) {
        throw new HttpException(
          { message: 'Broadcast not found' },
          HttpStatus.NOT_FOUND
        );
      }

      // Check if broadcast has failed status
      if (broadcast.status !== BroadcastStatus.FAILED && broadcast.status !== BroadcastStatus.PARTIALLY_FAILED) {
        throw new HttpException(
          { message: 'Only failed or partially failed broadcasts can be retried' },
          HttpStatus.BAD_REQUEST
        );
      }

      // Reset status to pending for retry
      await this.Model.BroadcastModel.updateOne(
        { _id: broadcast._id },
        {
          $set: {
            status: BroadcastStatus.PENDING,
            success_count: 0,
            failed_count: 0,
            pending_count: broadcast.total_recipients,
            error_message: null,
            started_at: null,
            completed_at: null,
            updated_at: +new Date()
          }
        }
      );

      // Process broadcast again
      const retryBroadcastId = (broadcast as any)._id.toString();
      this.processBroadcast(retryBroadcastId).catch(error => {
        console.error('Broadcast retry processing failed:', error);
      });

      return this.CommonService.successResponse('Broadcast retry started', {
        broadcast_id: broadcast._id
      });
    } catch (error) {
      throw error;
    }
  }

  /**
   * Saves an in-app notification and pushes it to the user's devices, translated into their language.
   * Title and message are i18n/notification keys; `meta` fills their {placeholders}.
   * Never throws: a failed push must not fail the action that triggered it.
   */
  async notifyUser(params: { userId: Types.ObjectId | string; sentBy: Types.ObjectId | string; type: NotificationType; titleKey: string; messageKey: string; meta?: Record<string, any> }) {
    try {
      const userId = new Types.ObjectId(params.userId as any);

      await this.Model.notifications.create({
        type: params.type,
        title: params.titleKey,
        message: params.messageKey,
        meta: params.meta ?? null,
        sent_by: new Types.ObjectId(params.sentBy as any),
        sent_to: userId,
        is_read: false,
        created_at: +new Date(),
        updated_at: +new Date()
      });

      const [user, sessions] = await Promise.all([
        this.Model.UserModel.findById(userId).select('language').lean(),
        this.Model.SessionModel.find({ user_id: userId, fcm_token: { $exists: true, $ne: null } }).select('fcm_token').lean()
      ]);
      const tokens = sessions.map((s: any) => s.fcm_token).filter(Boolean);
      if (!tokens.length) return;

      const lang = user?.language || 'en';
      await this.CommonService.sendPushNotification(tokens, {
        type: params.type,
        title: this.CommonService.translateKey(lang, params.titleKey, I18nType.NOTIFICATION, params.meta),
        message: this.CommonService.translateKey(lang, params.messageKey, I18nType.NOTIFICATION, params.meta),
        sent_to: userId.toString(),
        sent_by: String(params.sentBy)
      });
    } catch (error) {
      console.error('Failed to notify user:', error?.message);
    }
  }
}
