import { Injectable } from "@nestjs/common"
import { DataSource } from "typeorm"
import { SendNotificationDto } from "./dto/send-notification.dto"
import { SaveFcmTokenDto } from "./dto/save-fcm-token.dto"
import { MarkReadDto } from "./dto/mark-read.dto"
import { FcmTokenRepository } from "./repositories/fcm-token.repository"
import { NotificationRepository } from "./repositories/notification.repository"
import {
  errorMessage,
  isEmpty,
  successMessage,
  validationMessage,
} from "../../utils/helpers"
import {
  failureResponse,
  successResponse,
} from "../../common/response/response"
import { code } from "../../common/response/response.code"
import { messageKey } from "../../constants/message-keys"
import { verifyJwtToken } from "../../utils/jwt"
import admin from "../../firebase/firebase-admin"

@Injectable()
export class NotificationService {
  constructor(
    private readonly fcmTokenRepository: FcmTokenRepository,
    private readonly notificationRepository: NotificationRepository,
    private readonly dataSource: DataSource,
  ) {}

  /**
   * Send push notification using Firebase Admin SDK
   */
  async sendNotification(dto: SendNotificationDto) {
    try {
      const message = {
        notification: {
          title: dto.title,
          body: dto.description,
        },
        tokens: dto.tokens,
        data: dto.data
          ? Object.fromEntries(
              Object.entries(dto.data).map(([key, value]) => [
                key,
                String(value),
              ]),
            )
          : undefined,
      }

      const response = await admin.messaging().sendEachForMulticast(message)

      return successResponse(
        code.SUCCESS,
        successMessage(messageKey.data_retrieve, { ":data": "Notification" }),
        {
          successCount: response.successCount,
          failureCount: response.failureCount,
        } as any,
      )
    } catch (err) {
      return failureResponse(code.ERROR, errorMessage(messageKey.exception))
    }
  }

  /**
   * Create notification record in database
   */
  async createNotification(payload: {
    title: string
    description: string
    company_id: number
    employee_id?: number
    type?: string
  }) {
    const notification = {
      ...payload,
      type: payload.type || "GENERAL",
      is_read: 0,
    }

    return await this.notificationRepository.save(notification)
  }

  /**
   * Save or update FCM token
   */
  async saveFcmToken(dto: SaveFcmTokenDto, token: string) {
    const decoded = verifyJwtToken(token)

    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    try {
      // Use decoded token values if not provided in DTO
      const tokenData = {
        fcm_token: dto.fcm_token,
        company_id: decoded.company_id,
        user_id: decoded.user_id,
        employee_id: decoded.employee_id,
      }

      // Check if token already exists for this user/employee and company
      const whereCondition: any = {
        company_id: tokenData.company_id,
      }

      if (tokenData.user_id) {
        whereCondition.user_id = tokenData.user_id
      }

      if (tokenData.employee_id) {
        whereCondition.employee_id = tokenData.employee_id
      }

      const existingToken: any = await this.fcmTokenRepository.getByParams({
        where: whereCondition,
        findOne: true,
      })

      if (existingToken) {
        // Update existing token
        await this.fcmTokenRepository.save({
          id: existingToken.id,
          fcm_token: tokenData.fcm_token,
          updated_by: decoded.user_id,
        })
      } else {
        // Create new token
        await this.fcmTokenRepository.save({
          ...tokenData,
          created_by: decoded.user_id,
        })
      }

      return successResponse(
        code.SUCCESS,
        successMessage(messageKey.data_add, { ":data": "FCM Token" }),
      )
    } catch (error) {
      return failureResponse(code.ERROR, errorMessage(messageKey.exception))
    }
  }

  /**
   * Mark notification as read
   */
  async markNotificationAsRead(dto: MarkReadDto, token: string) {
    const decoded = verifyJwtToken(token)

    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    let whereNull: any
    const whereCondition: any = {
      company_id: decoded.company_id,
      is_read: 0,
    }

    if (isEmpty(decoded.employee_id)) {
      whereNull = ["employee_id"]
    } else {
      whereCondition.employee_id = decoded.employee_id
    }

    if (dto.notification_id) {
      whereCondition.id = dto.notification_id
    }

    try {
      if (dto.mark_all_read) {
        // Mark all notifications as read for this employee and company
        const notifications: any =
          await this.notificationRepository.getByParams({
            where: whereCondition,
            whereNull,
          })

        if (!isEmpty(notifications)) {
          for (const notification of notifications) {
            await this.notificationRepository.save({
              id: notification.id,
              is_read: 1,
              updated_by: decoded.user_id,
            })
          }
        }
      } else if (dto.notification_id) {
        // Mark specific notification as read
        const notification: any = await this.notificationRepository.getByParams(
          {
            where: whereCondition,
            findOne: true,
            whereNull,
          },
        )

        if (isEmpty(notification)) {
          return failureResponse(
            code.VALIDATION,
            errorMessage(messageKey.data_not_found, {
              ":data": "Notification",
            }),
          )
        }

        await this.notificationRepository.save({
          id: notification.id,
          is_read: 1,
          updated_by: decoded.user_id,
        })
      } else {
        return failureResponse(
          code.VALIDATION,
          validationMessage(messageKey.field_required, {
            ":field": "notification_id or mark_all_read",
          }),
        )
      }

      return successResponse(
        code.SUCCESS,
        successMessage(messageKey.data_update, { ":data": "Notification" }),
      )
    } catch (error) {
      return failureResponse(code.ERROR, errorMessage(messageKey.exception))
    }
  }

  /**
   * Helper method to send push notification and save to database for a single employee
   */
  async sendAndSaveNotification(payload: {
    title: string
    description: string
    company_id: number
    employee_id?: number
    data?: Record<string, any>
    type?: string
  }) {
    try {
      let tokens = []

      if (payload.employee_id) {
        // Get FCM token for the target employee
        const fcmTokens: any = await this.fcmTokenRepository.getByParams({
          where: {
            company_id: payload.company_id,
            employee_id: payload.employee_id,
          },
          select: ["fcm_token", "employee_id"],
        })

        const fcmTokensArray = fcmTokens || []
        tokens = fcmTokensArray
          .filter((token: any) => token.fcm_token)
          .map((token: any) => token.fcm_token)
      } else {
        // Get FCM tokens for super admin (for admin notifications)
        const tokensQuery = `
          SELECT ft.fcm_token
          FROM fcm_tokens ft
          JOIN users u ON u.id = ft.user_id
          WHERE ft.company_id = $1 
            AND u.slug = 'super_admin'
            AND u.status = 1
            AND ft.fcm_token IS NOT NULL
            AND ft.deleted_at IS NULL
            AND u.deleted_at IS NULL
        `
        const fcmTokensArray = await this.dataSource.query(tokensQuery, [
          payload.company_id,
        ])
        tokens = fcmTokensArray
          .filter((token: any) => token.fcm_token)
          .map((token: any) => token.fcm_token)
      }

      // Send push notifications if tokens exist
      if (tokens.length > 0) {
        // Prepare data with type included
        const notificationData = payload.data ? { ...payload.data } : {}
        if (payload.type) {
          notificationData.type = payload.type
        }

        await this.sendNotification({
          title: payload.title,
          description: payload.description,
          tokens,
          data:
            Object.keys(notificationData).length > 0
              ? notificationData
              : undefined,
        })
      }

      // Save notification to database
      await this.createNotification({
        title: payload.title,
        description: payload.description,
        company_id: payload.company_id,
        employee_id: payload.employee_id,
        type: payload.type,
      })

      const target = payload.employee_id
        ? `employee ${payload.employee_id}`
        : "admin"
      console.log(`✅ Notification sent and saved for ${target}`)
      return { success: true, tokens_sent: tokens.length }
    } catch (error) {
      console.error("❌ Error in sendAndSaveNotification:", error.message)
      throw error
    }
  }

  /**
   * Helper method to send push notification and save to database for multiple employees
   * Note: Loop should be implemented where this function is used
   */
  async sendAndSaveNotificationToMultiple(payload: {
    title: string
    description: string
    company_id: number
    employee_ids: number[]
    data?: Record<string, any>
    type?: string
  }) {
    try {
      // Get FCM tokens for all target employees
      const tokensQuery = `
        SELECT fcm_token, employee_id
        FROM fcm_tokens 
        WHERE company_id = $1 
          AND employee_id = ANY($2)
          AND fcm_token IS NOT NULL
          AND deleted_at IS NULL
      `
      const fcmTokensArray = await this.dataSource.query(tokensQuery, [
        payload.company_id,
        payload.employee_ids,
      ])

      const tokens = fcmTokensArray
        .filter((token: any) => token.fcm_token)
        .map((token: any) => token.fcm_token)

      // Send push notifications if tokens exist
      if (tokens.length > 0) {
        // Prepare data with type included
        const notificationData = payload.data ? { ...payload.data } : {}
        if (payload.type) {
          notificationData.type = payload.type
        }

        await this.sendNotification({
          title: payload.title,
          description: payload.description,
          tokens,
          data:
            Object.keys(notificationData).length > 0
              ? notificationData
              : undefined,
        })
      }

      // Save notifications to database for each employee
      const notificationPromises = payload.employee_ids.map((employee_id) =>
        this.createNotification({
          title: payload.title,
          description: payload.description,
          company_id: payload.company_id,
          employee_id,
          type: payload.type,
        }),
      )

      await Promise.all(notificationPromises)

      console.log(
        `✅ Notifications sent and saved for ${payload.employee_ids.length} employees`,
      )
      return { success: true, tokens_sent: tokens.length }
    } catch (error) {
      console.error(
        "❌ Error in sendAndSaveNotificationToMultiple:",
        error.message,
      )
      throw error
    }
  }

  /**
   * List notifications for authenticated user
   */
  async listNotifications(token: string, page: number = 1, limit: number = 10) {
    const decoded = verifyJwtToken(token)

    if (!decoded) {
      return failureResponse(
        code.VALIDATION,
        validationMessage(messageKey.invalid_token),
      )
    }

    try {
      const skip = (page - 1) * limit
      const take = limit

      // Build where condition - only add employee_id if it exists in decoded token
      const whereCondition: any = {
        company_id: decoded.company_id,
      }

      let whereNull: any

      if (decoded.employee_id !== undefined && decoded.employee_id !== null) {
        // Employee specific notifications
        whereCondition.employee_id = decoded.employee_id
      } else {
        // Only global notifications (employee_id IS NULL)
        whereNull = ["employee_id"]
      }
      const notifications: any = await this.notificationRepository.getByParams({
        where: whereCondition,
        whereNull,
        orderBy: { created_at: "DESC" },
        skip,
        take,
      })

      const unreadWhereCondition = {
        ...whereCondition,
        is_read: 0,
      }

      const unreadCount: any = await this.notificationRepository.getByParams({
        where: unreadWhereCondition,
        whereNull,
        getCountOnly: true,
      })

      const total: any = await this.notificationRepository.getByParams({
        where: whereCondition,
        whereNull,
        getCountOnly: true,
      })

      return successResponse(
        code.SUCCESS,
        successMessage(messageKey.data_retrieve, { ":data": "Notifications" }),
        {
          notifications,
          pagination: {
            total,
            page,
            limit,
            totalPages: Math.ceil(total / limit),
          },
          unread_count: unreadCount,
        } as any,
      )
    } catch (error) {
      return failureResponse(code.ERROR, errorMessage(messageKey.exception))
    }
  }
}
