import { Message, MessageType } from '@backend/domain'
import { CustomerEventRepository, MessageRepository, OrderRepository, TableRepository } from '@backend/repository'
import { NotificationEventType } from '@core/types'
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common'
import { Between, FindManyOptions, In } from 'typeorm'
import { DASHBOARD_URL } from '../../../config'
import { EventService, FirebaseMessagingService } from '../../../global'
import { MessageMapper, MessageTypeMapper } from '../mapper'
import { MessageResponse, SendMessageRequest } from '../types'

@Injectable()
export class MessageService {
	constructor(
		private readonly _eventService: EventService,
		private readonly _tableRepository: TableRepository,
		private readonly _orderRepository: OrderRepository,
		private readonly _messageRepository: MessageRepository,
		private readonly _cloudMessagingService: FirebaseMessagingService,
		private readonly _customerEventRepository: CustomerEventRepository,
	) {}

	async findAll(tableIds?: string[], date?: Date): Promise<MessageResponse[]> {
		const filter: FindManyOptions<Message> = { where: {} }
		if (tableIds) {
			filter.where = { ...filter.where, tableId: In(tableIds) }
		}

		if (date) {
			const start = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0)
			const end = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 23, 59, 59)
			filter.where = { ...filter.where, createdAt: Between(start, end) }
		}

		const messages = await this._messageRepository.find(filter)
		return messages.map((_message) => MessageMapper.toResponse(_message))
	}

	async send(request: SendMessageRequest): Promise<MessageResponse> {
		let body = request.message
		if (!request.message) {
			switch (request.messageType) {
				case MessageType.request_waiter: {
					body = 'Table is calling for a waiter'
					break
				}
				case MessageType.request_payment: {
					body = 'Table wants to pay'
					break
				}
				case MessageType.request_order: {
					body = 'Table wants to place an order'
					break
				}
				case MessageType.order: {
					body = 'Table has place an order'
					break
				}
				case MessageType.card_payment_complete: {
					body = 'Table has paid via card'
					break
				}
				case MessageType.dish_ready_to_serve: {
					body = 'Dish is ready to serve'
					break
				}
				default: {
					body = ''
				}
			}
		}

		const order = await this._orderRepository.findOne({
			where: { id: request.orderId },
			relations: { tables: true, reservation: true },
			select: { id: true, tables: { id: true }, reservation: { id: true } },
		})
		if (!order) {
			throw new BadRequestException()
		}

		const message: Partial<Message> = {
			body,
			// TODO: Handle multiple tables
			tableId: order.tables[0].id,
			orderId: order.id,
			reservationId: order.reservation?.id,
			type: request.messageType,
		}

		const entity = await this._messageRepository.save(message)
		this._eventService.send(NotificationEventType.messages_updated)

		this.logCustomerEvent(request.messageType, order.id)
		this.sendNotification(entity)

		return MessageMapper.toResponse(entity)
	}

	async acknowledge(id: string): Promise<void> {
		const exists = await this._messageRepository.exist({ where: { id } })
		if (!exists) {
			throw new NotFoundException('Message not found')
		}

		await this._messageRepository.update({ id }, { acknowledged: true })
		this._eventService.send(NotificationEventType.messages_updated)
	}

	private async sendNotification(message: Message): Promise<void> {
		const table = await this._tableRepository.findOne({
			where: { id: message.tableId },
			relations: { subscribers: { user: true } },
			select: { id: true, name: true, subscribers: { id: true, user: { id: true, messagingToken: {} } } },
		})

		if (!table) {
			throw new BadRequestException()
		}

		const sendRequests: Array<Promise<void>> = []
		for (const subscriber of table.subscribers) {
			const user = subscriber.user
			if (user.messagingToken?.web) {
				sendRequests.push(
					this._cloudMessagingService.send({
						token: user.messagingToken.web,
						notification: { title: table.name, body: message.body, imageUrl: process.env.KADICON_LOGO },
						webpush: { fcmOptions: { link: DASHBOARD_URL } },
					}),
				)
			}

			if (user.messagingToken?.mobile) {
				sendRequests.push(
					this._cloudMessagingService.send({
						token: user.messagingToken.mobile,
						notification: { title: table.name, body: message.body, imageUrl: process.env.KADICON_LOGO },
						android: { priority: 'normal' },
					}),
				)
			}
		}

		await Promise.allSettled(sendRequests)
	}

	private async logCustomerEvent(messageType: MessageType, orderId: string): Promise<void> {
		const customerEvent = MessageTypeMapper.messageTypeToCustomerEventType(messageType)
		if (customerEvent) {
			await this._customerEventRepository.save({ type: customerEvent, orderId })
		}
	}
}
