import { HttpErrorResponse } from '@angular/common/http'
import { Component, inject } from '@angular/core'
import { NonNullableFormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'
import { MatAutocompleteModule, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'
import { AnyType, IDateRange } from '@core/types'
import {
	CreateReservationRequest,
	CustomerData,
	DefaultService,
	FindAvailableTablesByTimeSlotResponse,
	ReservationResponse,
	UpdateReservationRequest,
} from '@api-client/angular'
import { LoadingButtonDirective } from '@frontend/shared'
import { TranslocoModule } from '@jsverse/transloco'
import { NgSelectModule } from '@ng-select/ng-select'
import { HotToastService } from '@ngxpert/hot-toast'
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'
import { isArray, uniqBy } from 'es-toolkit/compat'
import {
	Observable,
	combineLatest,
	debounceTime,
	distinctUntilChanged,
	filter,
	finalize,
	of,
	switchMap,
	tap,
} from 'rxjs'
import { I18nService } from '../../../core'
import { ReservationDateTimeComponent } from '../reservation-datetime.component'
import { AddReservationModalData } from './add-reservation.types'
import { RESERVATION_MODAL_DATA } from './tokens'
import { LucideUser, LucideX } from '@lucide/angular'

/**
 * Component for adding a reservation, which is usually triggered via Modal
 */
@UntilDestroy({ checkProperties: true })
@Component({
	selector: 'kadi-add-reservation',
	templateUrl: './add-reservation.component.html',
	styles: [
		`
			:host {
				@apply w-full;
			}
		`,
	],
	imports: [
		ReactiveFormsModule,
		MatAutocompleteModule,
		TranslocoModule,
		NgSelectModule,
		ReservationDateTimeComponent,
		LoadingButtonDirective,
		LucideX,
		LucideUser,
	],
})
export class AddReservationComponent {
	private readonly _toast = inject(HotToastService)
	private readonly _i18nService = inject(I18nService)
	private readonly _apiService = inject(DefaultService)
	private readonly _formBuilder = inject(NonNullableFormBuilder)
	private readonly data = inject<AddReservationModalData>(RESERVATION_MODAL_DATA)

	form = this._formBuilder.group({
		noOfPersons: this._formBuilder.control(1, { validators: [Validators.required, Validators.min(1)] }),
		tableId: this._formBuilder.control<string | undefined>(undefined),
		tableCombinationId: this._formBuilder.control<string | undefined>(undefined),
		startDate: this._formBuilder.control(new Date(), { validators: Validators.required }),
		endDate: this._formBuilder.control(new Date(), { validators: Validators.required }),
		comment: this._formBuilder.control<string | undefined>(undefined),
		selectedDate: this._formBuilder.control<IDateRange>(
			{ start: undefined, end: undefined },
			{ validators: Validators.required },
		),
		customerData: this._formBuilder.group({
			name: this._formBuilder.control('', { validators: Validators.required }),
			email: this._formBuilder.control<string | undefined>(undefined),
			phoneNumber: this._formBuilder.control<string | undefined>(undefined),
		}),
	})

	tables: FindAvailableTablesByTimeSlotResponse[] = []
	reservation: ReservationResponse | undefined
	loading = false
	customers: CustomerData[] = []

	constructor() {
		this.reservation = this.data.reservation
		this.initForm()

		if (this.reservation) {
			this.loadTables(this.reservation)
		}

		combineLatest([this.f.selectedDate.valueChanges, this.f.noOfPersons.valueChanges])
			.pipe(
				untilDestroyed(this),
				switchMap(([{ start, end }, noOfPersons]) => {
					return start && end && noOfPersons
						? this._apiService.roomPlanControllerFindAvailableTablesByTimeSlot(start, end, noOfPersons)
						: of([])
				}),
			)
			.subscribe((tables) => {
				this.tables = tables
				if (tables.length > 0) {
					this.f.tableId.setValue(tables[0].id)
				} else {
					this.f.tableId.setValue(undefined)
				}
			})
	}

	get f() {
		return this.form.controls
	}

	close(): void {
		this.data.overlayRef.detach()
		this.data.overlayRef.dispose()
	}

	changeSeats(operation: 'Increment' | 'Decrement'): void {
		const control = this.f['noOfPersons']
		const currentSeats = control.value
		switch (operation) {
			case 'Increment': {
				control.setValue(currentSeats + 1)
				break
			}
			case 'Decrement': {
				// only allow 1 as the minimum count
				if (currentSeats < 2) return
				control.setValue(currentSeats - 1)
				break
			}
		}
		this.form.markAsDirty()
	}

	save(): void {
		if (this.form.invalid) return
		this.loading = true

		const tableId = this.f.tableId.getRawValue()!
		const table = this.tables.find((table) => table.id === tableId)!

		if (table.type === 'Table') {
			this.f.tableId.setValue(tableId)
			this.f.tableCombinationId.setValue(undefined)
		} else {
			this.f.tableId.setValue(undefined)
			this.f.tableCombinationId.setValue(tableId)
		}

		let call$: Observable<ReservationResponse>

		if (this.reservation) {
			const request = this.form.getRawValue() as unknown as UpdateReservationRequest
			delete (request as AnyType).selectedDate
			request.id = this.reservation.id
			call$ = this._apiService.reservationControllerUpdate(this.reservation.id, request)
		} else {
			const request = this.form.getRawValue() as CreateReservationRequest
			delete (request as AnyType).selectedDate
			call$ = this._apiService.reservationControllerCreate(request)
		}

		call$
			.pipe(
				untilDestroyed(this),
				finalize(() => (this.loading = false)),
			)
			.subscribe({
				next: () => {
					this._toast.success(
						this._i18nService.translate(
							this.reservation?.id ? 'update_reservation_success' : 'add_reservation_success',
						),
					)
					this.close()
				},
				error: (response: HttpErrorResponse) => {
					if (isArray(response.message)) {
						this._toast.error(response.message[0])
					} else {
						this._toast.error(response.message)
					}
				},
			})
	}

	private initForm(): void {
		if (this.reservation) {
			this.form.patchValue({
				...this.reservation,
				selectedDate: { start: this.reservation.startDate, end: this.reservation.endDate },
			})
		}

		this.f.selectedDate.valueChanges.pipe(untilDestroyed(this)).subscribe((date: IDateRange) => {
			if (date.start) this.f.startDate.setValue(date.start)
			if (date.end) this.f.endDate.setValue(date.end)
		})

		this.f.customerData.controls.name.valueChanges.pipe(this.customerDataPipe('name')).subscribe((customers) => {
			this.customers = uniqBy(customers, 'name')
		})

		this.f.customerData.controls.email.valueChanges.pipe(this.customerDataPipe('email')).subscribe((customers) => {
			this.customers = uniqBy(customers, 'name')
		})

		this.f.customerData.controls.phoneNumber.valueChanges
			.pipe(this.customerDataPipe('phoneNumber'))
			.subscribe((customers) => {
				this.customers = uniqBy(customers, 'name')
			})
	}

	onCustomerSelected(event: MatAutocompleteSelectedEvent): void {
		const customer = event.option.value as CustomerData
		this.f.customerData.patchValue(customer)
		this.customers = []
	}

	private customerDataPipe(field: string): (source$: Observable<string | undefined>) => Observable<CustomerData[]> {
		return (source$) =>
			source$.pipe(
				untilDestroyed(this),
				tap(() => (this.customers = [])),
				filter((query) => query !== null && query !== undefined && query.length >= 3),
				distinctUntilChanged(),
				debounceTime(500),
				switchMap((query) => this._apiService.reservationControllerSearchCustomers(field, query as string)),
			)
	}

	private loadTables(reservation: ReservationResponse): void {
		const id = this.reservation?.tableCombinationId ?? this.reservation?.tableIds[0]
		const type = this.reservation?.tableCombinationId ? 'TableCombination' : 'Table'

		if (!id) return

		this._apiService
			.roomPlanControllerFindAvailableTableById(id, type)
			.pipe(untilDestroyed(this))
			.subscribe((table) => {
				this.tables = [table]

				if (reservation.tableIds.length === 1) {
					this.form.controls.tableId.setValue(reservation.tableIds[0])
				}
				this.form.controls.tableCombinationId.setValue(reservation.tableCombinationId)
			})
	}
}
