import { BaseProvider } from '#templates/base/provider';
import { DebtReminderPolicy } from '#models/DebtReminderPolicy';
import { DebtReminderPolicyTarget } from '#models/DebtReminderPolicyTarget';
import { Building } from '#models/Building';
import { Apartment } from '#models/Apartment';
import { Room } from '#models/Room';
import { Resident } from '#models/Resident';
import sequelize from '#services/database/sequelize/sequelizeService';

export class DebtReminderPolicyProvider extends BaseProvider<DebtReminderPolicy> {
	public static instance: DebtReminderPolicyProvider;

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

	public static get model() {
		return DebtReminderPolicy;
	}

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

	public async getAllOptimized(options?: any) {
		const limit = Math.min(options?.pageSize || 20, 100);
		const offset = ((options?.page || 1) - 1) * limit;

		const order: any[] = options?.sortField
			? [[options.sortField, options.sortOrder || 'DESC']]
			: [['created_at', 'DESC']];

		try {
			const result = await DebtReminderPolicy.findAndCountAll({
				where: options?.filters || {},
				limit,
				offset,
				order,
			});

			return {
				count: result.count,
				rows: result.rows,
				page: options?.page || 1,
				pageSize: limit,
			};
		} catch (error) {
			await this.logError(error as Error);
			throw error;
		}
	}

	public async getByIdOptimized(id: string) {
		try {
			const policy = await DebtReminderPolicy.findOne({
				where: { id },
				include: [
					{
						model: DebtReminderPolicyTarget,
						as: 'debt_reminder_policy_targets',
						required: false,
					},
				],
			});

			if (!policy) return null;

			const targets = (policy.debt_reminder_policy_targets || []) as any[];
			const targetIdsByType: any = {
				BUILDING: [],
				APARTMENT: [],
				ROOM: [],
				RESIDENT: [],
			};

			targets.forEach((t) => {
				if (t.target_type !== 'ALL' && t.target_id) {
					targetIdsByType[t.target_type].push(t.target_id);
				}
			});

			// Fetch target details
			const [buildings, apartments, rooms, residents] = await Promise.all([
				targetIdsByType.BUILDING.length > 0
					? Building.findAll({ where: { id: targetIdsByType.BUILDING }, attributes: ['id', 'name', 'code'] })
					: [],
				targetIdsByType.APARTMENT.length > 0
					? Apartment.findAll({ where: { id: targetIdsByType.APARTMENT }, attributes: ['id', 'name', 'code', 'floor'] })
					: [],
				targetIdsByType.ROOM.length > 0
					? Room.findAll({ where: { id: targetIdsByType.ROOM }, attributes: ['id', 'name', 'code', 'floor'] })
					: [],
				targetIdsByType.RESIDENT.length > 0
					? Resident.findAll({ where: { id: targetIdsByType.RESIDENT }, attributes: ['id', 'full_name', 'phone', 'email', 'id_card'] })
					: [],
			]);

			const buildingMap = new Map(buildings.map((b) => [b.id, { name: b.name, code: b.code }]));
			const apartmentMap = new Map(apartments.map((a) => [a.id, { name: a.name, code: a.code, floor: a.floor }]));
			const roomMap = new Map(rooms.map((r) => [r.id, { name: r.name, code: r.code, floor: r.floor }]));
			const residentMap = new Map(residents.map((r) => [r.id, { full_name: r.full_name, phone: r.phone, email: r.email, id_card: r.id_card }]));

			return {
				id: policy.id,
				name: policy.name,
				reminder_type: policy.reminder_type,
				days_offset: policy.days_offset,
				send_time: policy.send_time,
				channels: policy.channels,
				status: policy.status,
				created_by: policy.created_by,
				updated_by: policy.updated_by,
				targets: targets.map((t: any) => {
					let targetDetails: any = null;
					if (t.target_type === 'BUILDING' && t.target_id) {
						targetDetails = buildingMap.get(t.target_id) || null;
					} else if (t.target_type === 'APARTMENT' && t.target_id) {
						targetDetails = apartmentMap.get(t.target_id) || null;
					} else if (t.target_type === 'ROOM' && t.target_id) {
						targetDetails = roomMap.get(t.target_id) || null;
					} else if (t.target_type === 'RESIDENT' && t.target_id) {
						targetDetails = residentMap.get(t.target_id) || null;
					}

					return {
						id: t.id,
						policy_id: t.policy_id,
						target_type: t.target_type,
						target_id: t.target_id,
						target_details: targetDetails,
						created_by: t.created_by,
						updated_by: t.updated_by,
						created_at: t.created_at,
						updated_at: t.updated_at,
					};
				}),
				created_at: policy.created_at,
				updated_at: policy.updated_at,
			};
		} catch (error) {
			await this.logError(error as Error);
			throw error;
		}
	}

	public async createWithTargets(
		data: {
			name: string;
			reminder_type: string;
			days_offset: number;
			send_time: string;
			channels: string[];
			status?: string;
			targets: Array<{ targetType: string; targetId?: string | null }>;
			created_by?: string | null;
		},
		transaction?: any,
	) {
		const t = transaction ?? (await sequelize.transaction());
		const ownTx = !transaction;

		try {
			const policy = await DebtReminderPolicy.create(
				{
					name: data.name,
					reminder_type: data.reminder_type,
					days_offset: data.days_offset,
					send_time: data.send_time,
					channels: data.channels,
					status: data.status ?? 'ACTIVE',
					created_by: data.created_by ?? null,
				} as any,
				{ transaction: t },
			);

			if (data.targets && data.targets.length > 0) {
				await DebtReminderPolicyTarget.bulkCreate(
					data.targets.map((target) => ({
						policy_id: policy.id,
						target_type: target.targetType,
						target_id: target.targetType === 'ALL' ? null : target.targetId,
						created_by: data.created_by ?? null,
					})) as any,
					{ transaction: t, ignoreDuplicates: true },
				);
			}

			if (ownTx) await t.commit();
			return policy;
		} catch (error) {
			if (ownTx) await t.rollback();
			throw error;
		}
	}

	public async updateWithTargets(
		id: string,
		data: {
			name?: string;
			reminder_type?: string;
			days_offset?: number;
			send_time?: string;
			channels?: string[];
			status?: string;
			targets?: Array<{ targetType: string; targetId?: string | null }>;
			updated_by?: string | null;
		},
	) {
		const t = await sequelize.transaction();
		try {
			const policy = await DebtReminderPolicy.findOne({ where: { id }, transaction: t });
			if (!policy) {
				await t.rollback();
				return null;
			}

			await policy.update(
				{
					...(data.name !== undefined && { name: data.name }),
					...(data.reminder_type !== undefined && { reminder_type: data.reminder_type }),
					...(data.days_offset !== undefined && { days_offset: data.days_offset }),
					...(data.send_time !== undefined && { send_time: data.send_time }),
					...(data.channels !== undefined && { channels: data.channels }),
					...(data.status !== undefined && { status: data.status }),
					...(data.updated_by !== undefined && { updated_by: data.updated_by }),
					updated_at: new Date(),
				},
				{ transaction: t },
			);

			if (data.targets !== undefined) {
				await DebtReminderPolicyTarget.destroy({ where: { policy_id: id }, transaction: t });
				if (data.targets.length > 0) {
					await DebtReminderPolicyTarget.bulkCreate(
						data.targets.map((target) => ({
							policy_id: id,
							target_type: target.targetType,
							target_id: target.targetType === 'ALL' ? null : target.targetId,
							created_by: data.updated_by ?? null,
							updated_by: data.updated_by ?? null,
						})) as any,
						{ transaction: t, ignoreDuplicates: true },
					);
				}
			}

			await t.commit();
			return policy;
		} catch (error) {
			await t.rollback();
			throw error;
		}
	}

	public async hardDelete(id: string) {
		const policy = await DebtReminderPolicy.findOne({ where: { id } });
		if (!policy) return null;

		await DebtReminderPolicyTarget.destroy({ where: { policy_id: id } });
		await policy.destroy();
		return policy;
	}
}
