import { AuthMethod, MultiTenancyInterceptor, UseAuth } from '@backend/auth'
import { CustomerData, ReservationSource } from '@backend/domain'
import {
	Body,
	Controller,
	Get,
	HttpCode,
	Param,
	ParseArrayPipe,
	ParseBoolPipe,
	ParseUUIDPipe,
	Patch,
	Post,
	Put,
	Query,
	UseInterceptors,
} from '@nestjs/common'
import {
	ApiBadRequestResponse,
	ApiInternalServerErrorResponse,
	ApiNoContentResponse,
	ApiNotFoundResponse,
	ApiOkResponse,
	ApiQuery,
	ApiTags,
	ApiUnprocessableEntityResponse,
} from '@nestjs/swagger'
import { ParseDatePipe } from '../../../common'
import { FreeTableCountResponse } from '../../order'
import { ReservationMapper } from '../mappers/reservation.mapper'
import { ReservationService, TimeslotService } from '../services'
import { CreateReservationRequest, ReservationResponse, TimeSlot, UpdateReservationRequest } from '../types'

@UseAuth(AuthMethod.JWT)
@UseInterceptors(MultiTenancyInterceptor)
@Controller('reservation')
@ApiTags('default')
export class ReservationController {
	constructor(
		private readonly _reservationService: ReservationService,
		private readonly _timeslotService: TimeslotService,
	) {}

	@Get()
	@ApiQuery({ name: 'date', type: Date, required: false })
	@ApiQuery({ name: 'tableIds', type: String, required: false })
	@ApiOkResponse({ type: ReservationResponse, isArray: true })
	@ApiBadRequestResponse()
	@ApiInternalServerErrorResponse()
	async findAll(
		@Query('date', new ParseDatePipe()) date?: Date,
		@Query('tableIds', new ParseArrayPipe({ optional: true })) tableIds?: string[],
	): Promise<ReservationResponse[]> {
		return this._reservationService.findAll(tableIds, date)
	}

	@Get('open')
	@ApiOkResponse({ type: ReservationResponse, isArray: true })
	@ApiBadRequestResponse()
	@ApiInternalServerErrorResponse()
	async findAllOpen(
		@Query('date', new ParseDatePipe()) date?: Date,
		@Query('tableIds', new ParseArrayPipe({ optional: true })) tableIds?: string[],
	): Promise<ReservationResponse[]> {
		return this._reservationService.findAllOpen(tableIds, date)
	}

	@Get('order')
	@ApiOkResponse({ type: ReservationResponse, isArray: true })
	@ApiBadRequestResponse()
	@ApiInternalServerErrorResponse()
	async findByOrderIds(@Query('orderIds', ParseArrayPipe) orderIds: string[]): Promise<ReservationResponse[]> {
		return this._reservationService.findByOrderIds(orderIds)
	}

	@Get(':id')
	@ApiOkResponse({ type: ReservationResponse })
	@ApiBadRequestResponse()
	@ApiNotFoundResponse()
	@ApiInternalServerErrorResponse()
	async findById(@Param('id', ParseUUIDPipe) id: string): Promise<ReservationResponse> {
		return this._reservationService.findById(id)
	}

	@Post()
	@ApiOkResponse({ type: ReservationResponse })
	@ApiBadRequestResponse()
	@ApiUnprocessableEntityResponse()
	@ApiInternalServerErrorResponse()
	async create(@Body() request: CreateReservationRequest): Promise<ReservationResponse> {
		const entity = await this._reservationService.create(request, ReservationSource.admin)
		return ReservationMapper.entityToResponse(entity)
	}

	@Put(':id')
	@ApiOkResponse({ type: ReservationResponse })
	@ApiBadRequestResponse()
	@ApiUnprocessableEntityResponse()
	@ApiInternalServerErrorResponse()
	async update(
		@Param('id', ParseUUIDPipe) id: string,
		@Body() request: UpdateReservationRequest,
	): Promise<ReservationResponse> {
		return this._reservationService.update(id, request)
	}

	@Get('customer/:field/:query')
	@ApiOkResponse({ type: CustomerData, isArray: true })
	@ApiBadRequestResponse()
	@ApiInternalServerErrorResponse()
	async searchCustomers(@Param('field') field: string, @Param('query') query: string): Promise<CustomerData[]> {
		return this._reservationService.searchCustomers(field, query)
	}

	@Get('slots/:date')
	@ApiQuery({ name: 'timezone', type: String, required: false })
	@ApiOkResponse({ type: TimeSlot, isArray: true })
	@ApiBadRequestResponse()
	@ApiInternalServerErrorResponse()
	async getTimeSlotsByDate(
		@Param('date', new ParseDatePipe()) date: Date,
		@Query('timezone') timezone?: string,
	): Promise<TimeSlot[]> {
		return this._timeslotService.getTimeSlotsByDate(date, timezone)
	}

	@Patch(':id/confirm')
	@HttpCode(204)
	@ApiNoContentResponse()
	@ApiBadRequestResponse()
	@ApiNotFoundResponse()
	@ApiInternalServerErrorResponse()
	async confirmReservation(@Param('id', ParseUUIDPipe) id: string): Promise<void> {
		return this._reservationService.confirmReservation(id)
	}

	@Patch(':id/unconfirm')
	@HttpCode(204)
	@ApiNoContentResponse()
	@ApiBadRequestResponse()
	@ApiNotFoundResponse()
	@ApiInternalServerErrorResponse()
	async unConfirmReservation(@Param('id', ParseUUIDPipe) id: string): Promise<void> {
		return this._reservationService.unConfirmReservation(id)
	}

	@Patch(':id/reject')
	@HttpCode(204)
	@ApiNoContentResponse()
	@ApiBadRequestResponse()
	@ApiNotFoundResponse()
	@ApiInternalServerErrorResponse()
	async rejectReservation(@Param('id', ParseUUIDPipe) id: string): Promise<void> {
		return this._reservationService.rejectReservation(id)
	}

	@Get('search/:query')
	@ApiOkResponse({ type: ReservationResponse, isArray: true })
	@ApiBadRequestResponse()
	@ApiInternalServerErrorResponse()
	async searchByQuery(
		@Param('query') query: string,
		@Query('open', new ParseBoolPipe({ optional: true })) onlyOpenReservations?: boolean,
	): Promise<ReservationResponse[]> {
		return this._reservationService.searchByQuery(query, onlyOpenReservations)
	}

	@Get('tables/free')
	@ApiOkResponse({ type: FreeTableCountResponse })
	@ApiBadRequestResponse()
	@ApiInternalServerErrorResponse()
	async freeTableCount(): Promise<FreeTableCountResponse> {
		return this._reservationService.freeTableCount()
	}

	@Get(':id/review')
	@HttpCode(204)
	@ApiNoContentResponse()
	@ApiBadRequestResponse()
	@ApiInternalServerErrorResponse()
	async sendReviewEmail(@Param('id', ParseUUIDPipe) id: string): Promise<void> {
		return this._reservationService.sendReviewEmail(id)
	}
}
