import { ChangeDetectionStrategy, ChangeDetectorRef, Component, forwardRef, inject } from '@angular/core'
import {
	AbstractControl,
	ControlValueAccessor,
	FormsModule,
	NG_VALIDATORS,
	NG_VALUE_ACCESSOR,
	ValidationErrors,
	Validator,
	Validators,
} from '@angular/forms'
import { IDateRange, ITimeRange } from '@core/types'
import { TimeUtils } from '@core/utils'
import { DefaultService, ScheduleSettings, TimeSlot } from '@api-client/angular'
import { TimeSlotPickerComponent, WeeklyDatePickerComponent } from '@frontend/shared'
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy'
import { addDays } from 'date-fns'
import { finalize } from 'rxjs'
import { AuthService } from '../../auth'
import { SettingsStore } from '../../+state'

@UntilDestroy({ checkProperties: true })
@Component({
	selector: 'kadi-reservation-datetime',
	template: `
		@if (settingsLoaded) {
			<kadi-weekly-date-picker
				[(ngModel)]="selectedDate"
				(ngModelChange)="onDateChanged($event)"
				[min]="currentDate"
				[schedule]="scheduleSettings!" />

			<kadi-time-slot-picker
				[(ngModel)]="selectedTime"
				(ngModelChange)="onTimeChanged($event)"
				[slots]="timeSlots"
				[defaultTime]="defaultReservationTime"
				[loading]="timeSlotsLoading" />
		}
	`,
	changeDetection: ChangeDetectionStrategy.OnPush,
	imports: [FormsModule, WeeklyDatePickerComponent, TimeSlotPickerComponent],
	providers: [
		{
			provide: NG_VALUE_ACCESSOR,
			useExisting: forwardRef(() => ReservationDateTimeComponent),
			multi: true,
		},
		{
			provide: NG_VALIDATORS,
			useExisting: forwardRef(() => ReservationDateTimeComponent),
			multi: true,
		},
	],
})
export class ReservationDateTimeComponent implements ControlValueAccessor, Validator {
	private readonly _authService = inject(AuthService)
	private readonly _apiService = inject(DefaultService)
	private readonly _changeDetectorRef = inject(ChangeDetectorRef)
	private readonly _settingsStore = inject(SettingsStore)

	tenantId: string
	selectedDate: Date | undefined
	selectedTime: ITimeRange | undefined
	timeSlots: TimeSlot[] = []
	currentDate = new Date()
	scheduleSettings: ScheduleSettings | undefined
	settingsLoaded = false
	timeSlotsLoading = false
	defaultReservationTime: number | undefined

	dateTimeRange: IDateRange | undefined

	/* eslint-disable @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function */
	onChange = (_date: IDateRange) => {}
	onTouched = () => {}
	onValidatorChange = () => {}
	/* eslint-enable @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function */

	constructor() {
		this.tenantId = this._authService.sessionInfo?.tenantId as string
		this.loadSettings()
	}

	//#region ControlValueAccessor method implementation
	writeValue(date: IDateRange | null | undefined): void {
		if (date === null || date === undefined) {
			this.selectedDate = undefined
			this.selectedTime = undefined
		} else {
			this.dateTimeRange = date
			if (date.start && date.end) {
				this.selectedDate = date.start
				this.selectedTime = {
					start: TimeUtils.extractTimeFromDate(date.start),
					end: TimeUtils.extractTimeFromDate(date.end),
				}
			}
		}
	}

	registerOnChange(onChange: (_date: IDateRange) => void): void {
		this.onChange = onChange
	}

	registerOnTouched(onTouched: () => void): void {
		this.onTouched = onTouched
	}
	//#endregion

	//#region Validator method implementation
	registerOnValidatorChange(fn: () => void): void {
		this.onValidatorChange = fn
	}

	validate(control: AbstractControl): ValidationErrors | null {
		if (!control.hasValidator(Validators.required)) return null

		return this.selectedDate === undefined ||
			this.selectedTime?.start === undefined ||
			this.selectedTime?.end === undefined
			? { required: true }
			: null
	}
	//#endregion

	onDateChanged(date: Date): void {
		this.updateTimeSlots(date)
		this.selectedTime = undefined
		this.updateDate({ start: date, end: date })
	}

	onTimeChanged(timeRange: ITimeRange | undefined): void {
		if (this.selectedDate === undefined) return

		const startDate = new Date(this.selectedDate)
		let endDate = new Date(this.selectedDate)

		if (timeRange === undefined) {
			startDate.setHours(0)
			startDate.setMinutes(0)
			endDate.setHours(0)
			endDate.setMinutes(0)
		} else {
			if (timeRange.start) {
				startDate.setHours(timeRange.start.hours)
				startDate.setMinutes(timeRange.start.minutes)
			}
			if (timeRange.end) {
				endDate.setHours(timeRange.end.hours)
				endDate.setMinutes(timeRange.end.minutes)
			}

			// If endTime is smaller than startTime, this means reservation spans to next day
			if (timeRange.start && timeRange.end && TimeUtils.isBefore(timeRange.end, timeRange.start)) {
				endDate = addDays(endDate, 1)
			}
		}

		this.updateDate({ start: startDate, end: endDate })
	}

	private updateDate(date: IDateRange): void {
		this.onTouched()
		this.onChange(date)
	}

	private loadSettings(): void {
		this._apiService
			.settingsControllerFind()
			.pipe(
				untilDestroyed(this),
				finalize(() => {
					this.settingsLoaded = true
					this._changeDetectorRef.markForCheck()
				}),
			)
			.subscribe({
				next: ({ schedule, reservation }) => {
					this.scheduleSettings = schedule
					this.defaultReservationTime = reservation?.defaultReservationTime
				},
			})
	}

	private updateTimeSlots(date: Date): void {
		this.timeSlotsLoading = true
		this._apiService
			.reservationControllerGetTimeSlotsByDate(date, this._settingsStore.timezone())
			.pipe(
				untilDestroyed(this),
				finalize(() => {
					this.timeSlotsLoading = false
					this._changeDetectorRef.markForCheck()
				}),
			)
			.subscribe({
				next: (timeSlots) => {
					this.timeSlots = timeSlots
				},
			})
	}
}
