import { Injectable, NotFoundException } from "@nestjs/common"
import { CreateNotificationDto } from "../dto/create-notification.dto"
import { NotificationRepository } from "../repositories/notification.repository"
import { CustomerNotificationRepository } from "../repositories/customer_notification.repository"
import { QueryParams } from "src/common/interfaces/query-params.interface"
import { failureResponse, successResponse } from "src/common/response/response"
import { code } from "src/common/response/response.code"
import { errorMessage, successMessage } from "src/utils/helpers"
import { messageKey } from "src/constants/message-keys"
import { Notification } from "../entities/notification.entity"
import { CustomerNotification } from "../entities/customer-notification.entity"
import { FirebaseNotificationService } from "./firebase-notification.service"
import { AuthService } from "../../auth/v1/auth.service"
import { SendNotificationDto } from "../dto/send-notification.dto"
import { SendPushByFcmTokenDto } from "../dto/send-push-by-fcm-token.dto"
import { CustomerLoginRepository } from "../../customers/repositories/customer-login.repository"
import { CustomerLogin } from "../../customers/entities/customer_login.entity"
import { ConfigService } from "@nestjs/config"
import { isEmpty } from "class-validator"
import { In } from "typeorm"
import { NotificationFilterDto } from "../dto/notification-filter.dto"

@Injectable()
export class NotificationService {
  constructor(
    private readonly notificationRepository: NotificationRepository,
    private readonly customerNotificationRepository: CustomerNotificationRepository,
    private readonly firebaseService: FirebaseNotificationService,
    private readonly authService: AuthService,
    private readonly customerLoginRepository: CustomerLoginRepository,
    private readonly configService: ConfigService,
  ) {}

  async create(createNotificationDto: CreateNotificationDto) {
    const createNotificationType = new Notification()
    Object.assign(createNotificationType, createNotificationDto)
    await this.notificationRepository.save(createNotificationType)
    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_add, { ":data": "Notification" }),
    )
  }

  async sendNotification(sendNotificationDto: SendNotificationDto) {
    let user_ids = sendNotificationDto.user_ids ?? []
    user_ids = [...new Set(user_ids)]
    if (!user_ids || user_ids.length === 0) {
      console.warn("No user IDs provided for notification")
      return
    }

    const getFcmTokens = await this.authService.getFcmTokens(user_ids)
    if (!getFcmTokens?.data?.length) {
      console.warn("No FCM tokens found for user IDs:", user_ids)
      return
    }

    const validUsers = getFcmTokens.data.filter(
      (user) => user.fcm_token && user.fcm_token.trim() !== "",
    )

    const tokens = [
      ...new Set(
        validUsers
          .map((user) => user.fcm_token?.trim())
          .filter((token) => token),
      ),
    ] as string[]

    let failedTokens: string[] = []

    if (tokens.length) {
      const response = await this.firebaseService.sendToMultipleDevices(
        tokens,
        sendNotificationDto.title,
        sendNotificationDto.message,
        sendNotificationDto.data,
      )
      failedTokens = response.failedTokens

      if (failedTokens.length > 0) {
        await this.authService.removeFcmTokens(failedTokens)
      }
    }

    for (const user of user_ids) {
      await this.notificationRepository.save({
        user_id: user,
        title: sendNotificationDto.title,
        message: sendNotificationDto.message,
        data: sendNotificationDto.data,
      })
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_add, { ":data": "Notifications" }),
    )
  }

  /** Test-only: sends FCM to the given token; does not persist anything. */
  async sendPushByFcmToken(dto: SendPushByFcmTokenDto) {
    const token = dto.fcm_token.trim()
    const raw = dto.data ?? {}
    const data: Record<string, string> = Object.fromEntries(
      Object.entries(raw).map(([k, v]) => [
        k,
        typeof v === "object" && v !== null ? JSON.stringify(v) : String(v),
      ]),
    )

    const messageId = await this.firebaseService.sendToDevice(
      token,
      dto.title,
      dto.message,
      data,
    )

    if (!messageId) {
      return failureResponse(
        code.BAD_REQUEST,
        "Failed to send push notification (check FCM token and Firebase config)",
      )
    }

    return successResponse(code.SUCCESS, "Push notification sent", {
      message_id: messageId,
    })
  }

  async sendCustomerNotification(sendNotificationDto: SendNotificationDto) {
    let customer_ids = sendNotificationDto.user_ids ?? []
    customer_ids = [...new Set(customer_ids)]

    if (!customer_ids || customer_ids.length === 0) {
      console.warn("No customer IDs provided for notification")
      return
    }

    const getFcmTokens = (await this.customerLoginRepository.getByParams({
      whereIn: { customer_id: customer_ids },
      select: ["fcm_token"],
    })) as CustomerLogin[]

    if (!getFcmTokens?.length) {
      console.warn("No FCM tokens found for customer IDs:", customer_ids)
      return
    }

    const validUsers = getFcmTokens.filter(
      (user) => user.fcm_token && user.fcm_token.trim() !== "",
    )

    const tokens = [
      ...new Set(
        validUsers
          .map((user) => user.fcm_token?.trim())
          .filter((token) => token),
      ),
    ] as string[]

    let failedTokens: string[] = []

    if (tokens.length) {
      const response = await this.firebaseService.sendToMultipleDevices(
        tokens,
        sendNotificationDto.title,
        sendNotificationDto.message,
        sendNotificationDto.data,
      )
      failedTokens = response.failedTokens

      if (failedTokens.length > 0) {
        await this.customerLoginRepository.remove({
          fcm_token: In(failedTokens),
        })
      }
    }

    for (const customer_id of customer_ids) {
      await this.customerNotificationRepository.save({
        customer_id,
        title: sendNotificationDto.title,
        message: sendNotificationDto.message,
        data: sendNotificationDto.data,
      })
    }

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_add, {
        ":data": "Customer Notifications",
      }),
    )
  }

  async findAll(filter: NotificationFilterDto, user_id: number) {
    const defaultPagination = {
      take: this.configService.get<number>("APP.pagination.take"),
      skip: this.configService.get<number>("APP.pagination.skip"),
    }

    // Always use user_id from token (parameter), not from filter
    const where: any = {
      user_id: Number(user_id), // Use the authenticated user's ID from token
    }

    // Only add is_read filter if provided
    if (filter?.is_read !== undefined && filter?.is_read !== null) {
      where.is_read = filter.is_read
    }

    const queryParams: any = {
      take: filter?.limit || defaultPagination.take,
      skip: filter?.skip || defaultPagination.skip,
      orderBy: { created_at: "DESC" },
      where,
      select: [
        "id",
        "user_id",
        "title",
        "message",
        "data",
        "is_read",
        "created_at",
      ],
    }

    const result = (await this.notificationRepository.getByParams(
      queryParams,
    )) as any

    if (isEmpty(result)) {
      return failureResponse(
        code.SUCCESS,
        errorMessage(messageKey.data_not_found, {
          ":data": "Notifications",
        }),
      )
    }

    // Use user_id from parameter (token), not from filter
    const unreadCount = await this.notificationRepository.countUnread(
      Number(user_id),
    )

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, {
        ":data": "Notifications",
      }),
      {
        ...result,
        unreadCount,
      },
    )
  }

  async findOne(id: number) {
    const notification = (await this.notificationRepository.getByParams({
      where: { id },
      findOne: true,
    })) as Notification
    if (!notification) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        errorMessage(messageKey.data_not_found, { ":data": "Notification" }),
      )
    }
    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_retrieve, { ":data": "Notification" }),
      notification,
    )
  }

  async markAsRead(id: number) {
    const notification = (await this.notificationRepository.getByParams({
      where: { id },
      findOne: true,
    })) as Notification

    if (!notification) {
      throw new NotFoundException(
        errorMessage(messageKey.data_not_found, { ":data": "Notification" }),
      )
    }

    await this.notificationRepository.updateReadStatus(id, true)

    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_update, {
        ":data": "Notification status",
      }),
    )
  }

  async remove(id: number) {
    const result = await this.notificationRepository.remove({ id })
    if (result && "affected" in result && result.affected === 0) {
      return failureResponse(
        code.DATA_NOT_FOUND,
        errorMessage(messageKey.data_not_found, { ":data": "Notification" }),
      )
    }
    return successResponse(
      code.SUCCESS,
      successMessage(messageKey.data_removed, { ":data": "Notification" }),
    )
  }

  async updateNotificationByUserIdAndTripId(userId: number, tripId: number) {
    return this.notificationRepository.updateNotificationByUserIdAndTripId(
      userId,
      tripId,
    )
  }

  async updateNotificationByTripIdExcludingUser(
    userId: number,
    tripId: number,
  ) {
    return this.notificationRepository.updateNotificationByTripIdExcludingUser(
      userId,
      tripId,
    )
  }

  async updateNotificationByTripId(tripId: number) {
    return this.notificationRepository.updateNotificationByTripId(tripId)
  }

  // Customer Notification methods
  async findAllCustomerNotifications(
    params: QueryParams<CustomerNotification>,
  ) {
    const result = (await this.customerNotificationRepository.getByParams({
      ...params,
      orderBy: { created_at: "DESC" },
    })) as { count: number; data: CustomerNotification[] }

    const unreadCount = await this.customerNotificationRepository.countUnread(
      Number(params.where.customer_id || 0),
    )

    return successResponse(code.SUCCESS, messageKey.data_retrieve, {
      count: result.count,
      data: result.data,
      pagination: {
        total: result.count,
        page: Math.ceil(params.skip / params.take) + 1,
        limit: params.take,
        totalPages: Math.ceil(result.count / params.take),
      },
      unreadCount,
    })
  }

  async findOneCustomerNotification(id: number) {
    const notification = (await this.customerNotificationRepository.getByParams(
      {
        where: { id },
        findOne: true,
      },
    )) as CustomerNotification
    if (!notification) {
      return failureResponse(code.DATA_NOT_FOUND, messageKey.data_not_found)
    }
    return successResponse(code.SUCCESS, messageKey.data_retrieve, notification)
  }

  async markCustomerNotificationAsRead(id: number) {
    const notification = (await this.customerNotificationRepository.getByParams(
      {
        where: { id },
        findOne: true,
      },
    )) as CustomerNotification

    if (!notification) {
      return failureResponse(code.DATA_NOT_FOUND, messageKey.data_not_found)
    }

    await this.customerNotificationRepository.updateReadStatus(id, true)

    return successResponse(code.SUCCESS, messageKey.data_update)
  }

  async removeCustomerNotification(id: number) {
    const result = await this.customerNotificationRepository.remove({ id })
    if (result && "affected" in result && result.affected === 0) {
      return failureResponse(code.DATA_NOT_FOUND, messageKey.data_not_found)
    }
    return successResponse(code.SUCCESS, messageKey.data_removed)
  }

  async updateNotificationByCustomerIdAndTripId(
    customerId: number,
    tripId: number,
  ) {
    return this.customerNotificationRepository.updateNotificationByCustomerIdAndTripId(
      customerId,
      tripId,
    )
  }

  async updateNotificationByTripIdExcludingCustomer(
    customerId: number,
    tripId: number,
  ) {
    return this.customerNotificationRepository.updateNotificationByTripIdExcludingCustomer(
      customerId,
      tripId,
    )
  }
}
