import { Application } from 'express';
import { Resource } from 'express-automatic-routes';
import { ResidentProvider } from '#providers/ResidentProvider';
import { ResidentStayProvider } from '#providers/ResidentStayProvider';
import { Req, Res } from '#interfaces/IApi';
import verify, { requireAdmin } from '#middlewares/auth';
import { validateId } from '#middlewares/validators';
import { validateZod } from '#middlewares/validators';
import {
	ResidentAddBedsBodySchema,
	ResidentUpdateBedsBodySchema,
} from '#contracts/resident/schema';
import { Errors } from '#interfaces/error';
import { GenericError } from '#interfaces/error/generic';
import { sendSuccess } from '#utils/responseUtils';
import { AuditLogService } from '#services/audit/auditLogService';
import { randomUUID } from 'crypto';

export default (_express: Application) => {
	const residentProvider = new ResidentProvider();
	const residentStayProvider = ResidentStayProvider.getInstance();
	return <Resource>{
		post: {
			middleware: [verify, requireAdmin, validateId, validateZod(ResidentAddBedsBodySchema)],
			handler: async (req: Req, res: Res) => {
				try {
					const resident = await residentProvider.getById({ id: req.params.id! });
					if (!resident) throw Errors.notFound('Resident');

					const requestId = randomUUID();
					const traceId = randomUUID();

					const options: any = {
						stay_type: req.body.stay_type,
						created_by: req.user?.id,
						updated_by: req.user?.id,
					};
					if (req.body.move_in_at) {
						options.move_in_at = new Date(req.body.move_in_at);
					}

					const stays = await residentStayProvider.addBedsToResident(req.params.id!, req.body.bed_ids, options);

					// Audit log for adding beds
					await AuditLogService.enqueueSystemAudit({
						requestId,
						traceId,
						actorId: req.user?.id || null,
						actorEmail: req.user?.email,
						actorRole: req.user?.roles?.[0],
						action: 'RESIDENT_BEDS_ADDED',
						module: 'RESIDENT',
						entityId: req.params.id!,
						entityType: 'Resident',
						description: `Added ${req.body.bed_ids.length} bed(s) to resident ${resident.full_name}`,
						severity: 'MEDIUM',
						payload: {
							residentId: req.params.id!,
							residentName: resident.full_name,
							bedIds: req.body.bed_ids,
							moveInAt: req.body.move_in_at,
							stayType: req.body.stay_type,
						},
					} as any);

					// Fetch updated data with residence info
					const updatedData = await residentProvider.getByIdWithResidence({ id: req.params.id! });

					return sendSuccess(res, updatedData);
				} catch (error) {
					return res.error(error);
				}
			},
		},
		put: {
			middleware: [verify, requireAdmin, validateId, validateZod(ResidentUpdateBedsBodySchema)],
			handler: async (req: Req, res: Res) => {
				try {
					const resident = await residentProvider.getById({ id: req.params.id! });
					if (!resident) throw Errors.notFound('Resident');

					const requestId = randomUUID();
					const traceId = randomUUID();

					const options: any = {
						move_out_reason: req.body.move_out_reason,
						move_out_notes: req.body.move_out_notes,
						move_in_at: new Date(),
						stay_type: 'permanent',
						updated_by: req.user?.id,
					};
					if (req.body.move_out_at) {
						options.move_out_at = new Date(req.body.move_out_at);
					}

					const result = await residentStayProvider.updateBedsForResident(req.params.id!, req.body.bed_ids, options);

					// Audit log for updating beds
					await AuditLogService.enqueueSystemAudit({
						requestId,
						traceId,
						actorId: req.user?.id || null,
						actorEmail: req.user?.email,
						actorRole: req.user?.roles?.[0],
						action: 'RESIDENT_BEDS_UPDATED',
						module: 'RESIDENT',
						entityId: req.params.id!,
						entityType: 'Resident',
						description: `Updated beds for resident ${resident.full_name}: ${result.added.length} added, ${result.removed.length} removed`,
						severity: 'MEDIUM',
						payload: {
							residentId: req.params.id!,
							residentName: resident.full_name,
							bedIds: req.body.bed_ids,
							added: result.added.length,
							removed: result.removed.length,
							moveOutReason: req.body.move_out_reason,
						},
					} as any);

					// Fetch updated data with residence info
					const updatedData = await residentProvider.getByIdWithResidence({ id: req.params.id! });

					return sendSuccess(res, updatedData);
				} catch (error) {
					return res.error(error);
				}
			},
		},
		delete: {
			middleware: [verify, requireAdmin, validateId],
			handler: async (req: Req, res: Res) => {
				try {
					const resident = await residentProvider.getById({ id: req.params.id! });
					if (!resident) throw Errors.notFound('Resident');

					const { bed_ids, move_out_at, move_out_reason, move_out_notes } = req.body;

					if (!bed_ids || !Array.isArray(bed_ids) || bed_ids.length === 0) {
						throw new GenericError('VALIDATION_ERROR', {
							message: 'bed_ids is required and must be a non-empty array',
						});
					}

					const requestId = randomUUID();
					const traceId = randomUUID();	

					const options: any = {
						move_out_reason,
						move_out_notes,
						updated_by: req.user?.id,
					};
					if (move_out_at) {
						options.move_out_at = new Date(move_out_at);
					}

					const stays = await residentStayProvider.removeBedsFromResident(req.params.id!, bed_ids, options);

					// Audit log for removing beds
					await AuditLogService.enqueueSystemAudit({
						requestId,
						traceId,
						actorId: req.user?.id || null,
						actorEmail: req.user?.email,
						actorRole: req.user?.roles?.[0],
						action: 'RESIDENT_BEDS_REMOVED',
						module: 'RESIDENT',
						entityId: req.params.id!,
						entityType: 'Resident',
						description: `Removed ${bed_ids.length} bed(s) from resident ${resident.full_name}`,
						severity: 'MEDIUM',
						payload: {
							residentId: req.params.id!,
							residentName: resident.full_name,
							bedIds: bed_ids,
							moveOutAt: move_out_at,
							moveOutReason: move_out_reason,
						},
					} as any);

					// Fetch updated data with residence info
					const updatedData = await residentProvider.getByIdWithResidence({ id: req.params.id! });

					return sendSuccess(res, updatedData);
				} catch (error) {
					return res.error(error);
				}
			},
		},
	};
};
