import { SessionStore } from '@backend/auth'
import { Order, OrderState, Reservation } from '@backend/domain'
import {
	MessageRepository,
	OrderRepository,
	ReservationRepository,
	SettingsRepository,
	TableRepository,
} from '@backend/repository'
import { ReservationState } from '@core/types'
import { sortReservationsByState } from '@core/utils'
import { ForbiddenException, Injectable } from '@nestjs/common'
import { addHours, format, subHours, subMinutes } from 'date-fns'
import { Between, In } from 'typeorm'
import { OrderMapper } from '../order'
import { ReservationMapper } from '../reservation'
import { TableMapper } from '../room-plan'
import { DashboardMessageView, DashboardTableView, TableState } from './types'

@Injectable()
export class DashboardService {
	constructor(
		private readonly _tableRepository: TableRepository,
		private readonly _settingsRepository: SettingsRepository,
		private readonly _messageRepository: MessageRepository,
		private readonly _reservationRepository: ReservationRepository,
		private readonly _orderRepository: OrderRepository,
	) {}

	async getTablesView(): Promise<DashboardTableView[]> {
		const tenantId = SessionStore.tenantId

		if (!tenantId) throw new ForbiddenException()

		let dashboardReservationTimeLimit = await this._settingsRepository
			.findOne({ select: { reservation: { dashboardReservationTimeLimit: true } } })
			.then((settings) => settings?.reservation.dashboardReservationTimeLimit)

		// Set a default time of 3 hours if limit is not set
		dashboardReservationTimeLimit = dashboardReservationTimeLimit ?? 3

		// Filters for selecting approved and unconfirmed reservations,
		// Select between 60 minutes past and dashboardReservationTimeLimit hours from now
		const reservationFilterStart = subMinutes(Date.now(), 60)
		const reservationFilterEnd = addHours(Date.now(), dashboardReservationTimeLimit)

		const tables = await this._tableRepository.find()
		const reservations = await this._reservationRepository.find({
			where: [
				{ state: ReservationState.seated },
				{
					state: In([ReservationState.unconfirmed, ReservationState.approved]),
					startDate: Between(reservationFilterStart, reservationFilterEnd),
				},
			],
			relations: { tables: true },
		})
		const orders = await this._orderRepository.find({
			where: [{ state: OrderState.open }],
			relations: { reservation: { tables: true }, tables: true, items: { menuItem: true } },
		})

		const tableViews: DashboardTableView[] = []
		for (const table of tables) {
			let tableReservation: Reservation | undefined
			const tableOrder = orders.find((order) => order.tables.some((_table) => _table.id === table.id))
			// eslint-disable-next-line unicorn-x/prefer-ternary
			if (tableOrder && tableOrder.reservation) {
				tableReservation = tableOrder.reservation
			} else {
				// Sort reservations by seated, approved and then unconfirmed
				tableReservation = sortReservationsByState(reservations)
					.reverse()
					.find((reservation) => reservation.tables?.some((_table) => _table.id === table.id))
			}

			const tableView: DashboardTableView = {
				tableState: this.getTableState(tableReservation, tableOrder),
				tableRef: TableMapper.entityToResponse(table),
				reservation: tableReservation ? ReservationMapper.entityToResponse(tableReservation) : undefined,
				order: tableOrder ? OrderMapper.entityToResponse(tableOrder) : undefined,
			}

			tableViews.push(tableView)
		}

		return tableViews
	}

	async getMessagesView(): Promise<DashboardMessageView[]> {
		const tenantId = SessionStore.tenantId
		const userId = SessionStore.userId

		if (!tenantId || !userId) throw new ForbiddenException()

		// Select between 24 hours past and current time
		const dateFilterStart = format(subHours(Date.now(), 24), 'yyyy-MM-dd HH:mm:ss') // set in 24h format
		const dateFilterEnd = format(Date.now(), 'yyyy-MM-dd HH:mm:ss')

		const result = await this._messageRepository.query<DashboardMessageView[]>(`
		SELECT
			DISTINCT ON ("message".id)
			"message".id,
			"message".body,
			"message".type,
			"message".acknowledged,
			"message"."createdAt" as "timestamp",
			"table".name as "tableName",
			"reservation"."customerName"
		FROM "message" "message"
		JOIN "table"
			ON "message"."tableId" = "table"."id"
		JOIN "table_subscriber"
			ON "table"."id" = "table_subscriber"."tableId"
		LEFT JOIN "reservation"
			ON "message"."reservationId" = "reservation"."id"
		WHERE
			"message"."tenantId" = '${tenantId}'
			AND "message"."createdAt" BETWEEN '${dateFilterStart}' AND '${dateFilterEnd}'
			AND table_subscriber."userId" = '${userId}'
		`)

		return result
	}

	private getTableState(reservation?: Reservation, order?: Order): TableState {
		return order
			? TableState.taken
			: reservation
				? // eslint-disable-next-line unicorn-x/no-nested-ternary
					reservation.state === ReservationState.approved
					? TableState.approved
					: TableState.unconfirmed
				: TableState.free
	}
}
