import { inject, Injectable } from '@angular/core'
import { ActivatedRoute, Router } from '@angular/router'
import { NotificationEventType } from '@core/types'
import { sortReservationsByState } from '@core/utils'
import { WSService } from '@frontend/ws'
import {
	DefaultService,
	OrderResponse,
	ReservationResponse,
	ScheduleSettings,
	TableResponse,
} from '@api-client/angular'
import { HotToastService } from '@ngxpert/hot-toast'
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'
import { ComponentStore } from '@ngrx/component-store'
import { sortBy } from 'es-toolkit/compat'
import { combineLatest, debounceTime, filter, map, Observable, of, switchMap, tap, timer } from 'rxjs'
import { I18nService } from '../../core'
import { ReservationSAMService } from './reservation-sam.service'
import {
	ReservationDto,
	ReservationFilter,
	ReservationFilterStates,
	ReservationLayout,
	ReservationLayoutType,
	ReservationView,
} from './reservation.types'
import { LucideCalendar, LucideList, LucideTableProperties } from '@lucide/angular'

export const RESERVATION_VIEWS: ReservationLayout[] = [
	{ type: ReservationLayoutType.LIST, icon: LucideList },
	{ type: ReservationLayoutType.GRID, icon: LucideTableProperties },
	{ type: ReservationLayoutType.CALENDER, icon: LucideCalendar },
]

interface ReservationStoreState {
	reservations: ReservationResponse[]
	orders: OrderResponse[]
	tables: TableResponse[]
	layout: ReservationLayout
	stateFilter: ReservationFilter | undefined
	filterDate: Date
	searchTerm: string | undefined
	schedule: ScheduleSettings | undefined
}

@UntilDestroy({ checkProperties: true })
@Injectable()
export class ReservationStore extends ComponentStore<ReservationStoreState> {
	readonly layout$ = this.select((state) => state.layout)
	readonly tables$ = this.select((state) => state.tables)
	readonly stateFilter$ = this.select((state) => state.stateFilter)
	readonly filterDate$ = this.select((state) => state.filterDate)
	readonly searchTerm$ = this.select((state) => state.searchTerm)
	readonly schedule$ = this.select((state) => state.schedule)
	private readonly _router = inject(Router)
	private readonly _wsService = inject(WSService)
	private readonly _toast = inject(HotToastService)
	private readonly _i18nService = inject(I18nService)
	private readonly _apiService = inject(DefaultService)
	private readonly _activeRoute = inject(ActivatedRoute)
	private readonly _samService = inject(ReservationSAMService)
	//#region Selectors
	readonly reservations$: Observable<ReservationView[]> = this.select(
		({ orders, reservations, searchTerm, stateFilter, tables }) => {
			if (searchTerm) {
				reservations = reservations.filter((reservation) =>
					reservation.customerData.name?.toLowerCase().includes(searchTerm.toLowerCase()),
				)
			}

			let dtos: ReservationDto[] = reservations.map((reservation) => ({
				...reservation,
				isLate: this.isLateReservation(reservation),
			}))

			if (stateFilter) {
				dtos =
					stateFilter === ReservationFilterStates.Late
						? dtos.filter((reservation) => reservation.isLate)
						: dtos.filter((reservation) => !reservation.isLate && reservation.state === stateFilter)
			}

			// Sort by time and state
			dtos = sortReservationsByState(dtos)
			dtos = sortBy(dtos, 'startDate')

			return dtos.map((reservation) => {
				// TODO:
				const table = tables.find((table) => reservation.tableIds.includes(table.id))!
				const resTables = tables.filter((table) => reservation.tableIds?.includes(table.id)) ?? []
				const order = orders.find((order) => order.reservationId === reservation.id)

				return {
					ref: reservation,
					table,
					tables: resTables,
					tableNames: resTables.length > 0 ? resTables.map((table) => table.name) : table ? [table.name] : [],
					order,
					state: reservation.state,
					stateService: this._samService.createState(reservation, order, tables),
				} as ReservationView
			})
		},
	)
	readonly actionLoading$ = this._samService.actionLoading$
	readonly actionSucces$ = this._samService.actionSucces$

	constructor() {
		super({
			reservations: [],
			orders: [],
			tables: [],
			layout: RESERVATION_VIEWS[0],
			stateFilter: undefined,
			filterDate: new Date(),
			searchTerm: undefined,
			schedule: undefined,
		})

		this.loadData()

		combineLatest([this.filterDate$, this.actionSucces$])
			.pipe(untilDestroyed(this), debounceTime(500))
			.subscribe({
				next: ([date]) => this.loadReservations(date),
			})

		this._wsService.messages$
			.pipe(
				untilDestroyed(this),
				debounceTime(500),
				filter(
					({ event }) =>
						event === NotificationEventType.orders_updated || event === NotificationEventType.reservations_updated,
				),
			)
			.subscribe({
				next: () => this.loadReservations(this.state().filterDate),
			})

		// Check for late reservations every 10 seconds
		timer(10_000).pipe(
			untilDestroyed(this),
			switchMap(() => this.reservations$),
			map((reservations) =>
				reservations.map((reservation) => ({
					...reservation.ref,
					isLate: this.isLateReservation(reservation.ref),
				})),
			),
			tap((reservations) => {
				this.patchState({ reservations })
			}),
		)
	}
	//#endregion

	//#region Public Methods
	selectLayout(layout: ReservationLayout): void {
		this.patchState({ layout })
	}

	filterByDate(filterDate: Date): void {
		this.patchState({ filterDate })
	}

	search(searchTerm: string | undefined) {
		this.patchState({ searchTerm })
	}

	setStateFilter(stateFilter: ReservationFilter | undefined): void {
		this.patchState({ stateFilter })
	}

	sendReviewEmail(reservation: ReservationResponse): void {
		if (!reservation.customerData.email) return

		this._apiService
			.reservationControllerSendReviewEmail(reservation.id)
			.pipe(
				untilDestroyed(this),
				this._toast.observe({
					success: this._i18nService.translate('review_email_sent_success'),
					error: this._i18nService.translate('review_email_sent_error'),
				}),
			)
			.subscribe()
	}
	//#endregion

	//#region Private Methods
	private loadData(): void {
		this._apiService
			.roomPlanControllerFindAllTables()
			.pipe(untilDestroyed(this))
			.subscribe({
				next: (tables) => this.patchState({ tables }),
			})

		this._apiService
			.settingsControllerFind()
			.pipe(untilDestroyed(this))
			.subscribe({
				next: ({ schedule }) => this.patchState({ schedule }),
			})

		this._activeRoute.queryParams.pipe(untilDestroyed(this)).subscribe({
			next: (params) => {
				const { layout, date } = params
				if (layout) {
					const selectedLayout = RESERVATION_VIEWS.find((_layout) => _layout.type === layout)
					if (selectedLayout) this.patchState({ layout: selectedLayout })
				}
				if (date) this.filterByDate(new Date(date))
			},
		})
	}

	private loadReservations(date: Date): void {
		this._apiService
			.reservationControllerFindAll(date, undefined)
			.pipe(
				untilDestroyed(this),
				map((reservations) => sortBy(reservations, (reservation) => reservation.startDate)),
				switchMap((reservations) => {
					if (reservations.length > 0) {
						const reservationIds = reservations.map((reservation) => reservation.id)
						return this._apiService
							.orderControllerFindOrdersByReservationIds(reservationIds)
							.pipe(map((orders) => ({ reservations, orders })))
					} else return of({ reservations, orders: [] })
				}),
			)
			.subscribe({
				next: ({ orders, reservations }) => {
					this.patchState({ orders, reservations })
					this.reservationActionsFromQueryParams()
				},
			})
	}

	private reservationActionsFromQueryParams(): void {
		const { id, action } = this._activeRoute.snapshot.queryParams

		if (id && action) {
			if (action === 'accept') {
				this._apiService
					.reservationControllerConfirmReservation(id)
					.pipe(untilDestroyed(this))
					.subscribe({
						next: () => {
							this._toast.success('Reservation has been confirmed')
						},
						error: (error) => {
							this._toast.error(error.message)
						},
					})
			} else if (action === 'decline') {
				this._apiService
					.reservationControllerRejectReservation(id)
					.pipe(untilDestroyed(this))
					.subscribe({
						next: () => {
							this._toast.success('Reservation has been rejected')
						},
						error: (error) => {
							this._toast.error(error.message)
						},
					})
			}
		}

		// Clear query params
		this._router.navigate([], { queryParams: { id: null, action: null }, queryParamsHandling: 'merge' })
	}

	private isLateReservation(reservation: ReservationResponse): boolean {
		return reservation.state === ReservationResponse.StateEnum.approved && new Date(reservation.startDate) < new Date()
	}
	//#endregion
}
