import { BaseProvider } from '#templates/base/provider';
import { ResidentStay } from '#models/ResidentStay';
import { Op } from 'sequelize';
import { GenericError } from '#interfaces/error/generic';

export class ResidentStayProvider extends BaseProvider<ResidentStay> {
	public static instance: ResidentStayProvider;

	public static getInstance(): ResidentStayProvider {
		ResidentStayProvider.instance ??= new ResidentStayProvider();
		return ResidentStayProvider.instance;
	}

	public static get model() {
		return ResidentStay;
	}

	constructor() {
		super('ResidentStay');
	}

	async addBedsToResident(
		residentId: string,
		bedIds: string[],
		options?: {
			move_in_at?: Date;
			stay_type?: 'permanent' | 'temporary';
			created_by?: string;
			updated_by?: string;
			transaction?: any;
		},
	): Promise<ResidentStay[]> {
		return this.executeInTransaction(async (tx) => {
			const stays: ResidentStay[] = [];
			const createdBy = options?.created_by || options?.updated_by;
			const updatedBy = options?.updated_by || options?.created_by;

			for (const bedId of bedIds) {
				// Check if resident already has an active stay for this bed
				const existingStay = await ResidentStay.findOne({
					where: {
						resident_id: residentId,
						bed_id: bedId,
						move_out_at: null,
					},
					transaction: tx,
				});

				if (existingStay) {
					throw new GenericError('VALIDATION_ERROR', {
						message: `Resident already has an active stay for bed ${bedId}`,
						bedId,
					});
				}

				const stay = await this.create(
					{
						resident_id: residentId,
						bed_id: bedId,
						move_in_at: options?.move_in_at || new Date(),
						move_out_at: null,
						stay_type: options?.stay_type || 'permanent',
						created_by: createdBy,
						updated_by: updatedBy,
						created_at: new Date(),
						updated_at: new Date(),
					} as any,
					{ transaction: tx },
				);
				stays.push(stay);
			}

			return stays;
		}, options?.transaction);
	}

	async updateBedsForResident(
		residentId: string,
		newBedIds: string[],
		options?: {
			move_out_at?: Date;
			move_out_reason?: string;
			move_out_notes?: string;
			move_in_at?: Date;
			stay_type?: 'permanent' | 'temporary';
			updated_by?: string;
			transaction?: any;
		},
	): Promise<{ added: ResidentStay[]; removed: ResidentStay[] }> {
		return this.executeInTransaction(async (tx) => {
			const updatedBy = options?.updated_by;

			// Get all active stays for the resident
			const activeStays = await ResidentStay.findAll({
				where: {
					resident_id: residentId,
					move_out_at: null,
				},
				transaction: tx,
			});

			const currentBedIds = activeStays.map((stay) => stay.bed_id);

			// Find beds to remove (in current but not in new)
			const bedsToRemove = currentBedIds.filter((bedId) => !newBedIds.includes(bedId));

			// Find beds to add (in new but not in current)
			const bedsToAdd = newBedIds.filter((bedId) => !currentBedIds.includes(bedId));

			// Remove old stays
			const removed: ResidentStay[] = [];
			for (const bedId of bedsToRemove) {
				const stay = activeStays.find((s) => s.bed_id === bedId);
				if (stay) {
					const updateData: any = {
						move_out_at: options?.move_out_at || new Date(),
						updated_by: updatedBy,
						updated_at: new Date(),
					};
					if (options?.move_out_reason !== undefined) {
						updateData.move_out_reason = options.move_out_reason;
					}
					if (options?.move_out_notes !== undefined) {
						updateData.move_out_notes = options.move_out_notes;
					}
					await stay.update(updateData, { transaction: tx });
					removed.push(stay);
				}
			}

			// Add new stays
			const added: ResidentStay[] = [];
			for (const bedId of bedsToAdd) {
				const stay = await this.create(
					{
						resident_id: residentId,
						bed_id: bedId,
						move_in_at: options?.move_in_at || new Date(),
						move_out_at: null,
						stay_type: options?.stay_type || 'permanent',
						created_by: updatedBy,
						updated_by: updatedBy,
						created_at: new Date(),
						updated_at: new Date(),
					} as any,
					{ transaction: tx },
				);
				added.push(stay);
			}

			return { added, removed };
		}, options?.transaction);
	}

	async removeBedsFromResident(
		residentId: string,
		bedIds: string[],
		options?: {
			move_out_at?: Date;
			move_out_reason?: string;
			move_out_notes?: string;
			updated_by?: string;
			transaction?: any;
		},
	): Promise<ResidentStay[]> {
		return this.executeInTransaction(async (tx) => {
			const updatedBy = options?.updated_by;

			const stays = await ResidentStay.findAll({
				where: {
					resident_id: residentId,
					bed_id: { [Op.in]: bedIds },
					move_out_at: null,
				},
				transaction: tx,
			});

			for (const stay of stays) {
				const updateData: any = {
					move_out_at: options?.move_out_at || new Date(),
					updated_by: updatedBy,
					updated_at: new Date(),
				};
				if (options?.move_out_reason !== undefined) {
					updateData.move_out_reason = options.move_out_reason;
				}
				if (options?.move_out_notes !== undefined) {
					updateData.move_out_notes = options.move_out_notes;
				}
				await stay.update(updateData, { transaction: tx });
			}

			return stays;
		}, options?.transaction);
	}
}
