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

export class BillProvider extends BaseProvider<Bill> {
	public static instance: BillProvider;

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

	public static get model() {
		return Bill;
	}

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

	/**
	 * Create a bill with multiple bill items in a single transaction
	 */
	public async createWithItems(
		data: {
			code?: string;
			room_id: string;
			resident_id: string;
			billing_period: string;
			due_date: string;
			payment_method?: string;
			notes?: string;
			total_amount?: number;
			status?: string;
			created_by?: string;
			bill_items: Array<{
				fee_name: string;
				quantity?: number;
				unit_price: number;
			}>;
		},
		transaction?: any,
	) {
		const t = transaction ?? (await sequelize.transaction());
		const ownTx = !transaction;

		try {
			const bill = await Bill.create(
				{
					code: data.code,
					room_id: data.room_id,
					resident_id: data.resident_id,
					billing_period: data.billing_period,
					due_date: data.due_date,
					payment_method: data.payment_method ?? null,
					notes: data.notes ?? null,
					total_amount: data.total_amount ?? 0,
					status: data.status ?? 'pending',
					created_by: data.created_by ?? null,
				} as any,
				{ transaction: t },
			);

			if (data.bill_items && data.bill_items.length > 0) {
				const billItems = data.bill_items.map((item) => ({
					bill_id: bill.id,
					fee_name: item.fee_name,
					quantity: item.quantity ?? 1,
					unit_price: item.unit_price,
				})) as any;
				await BillItem.bulkCreate(billItems, { transaction: t });
			}

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

	/**
	 * Get all bills with optimized joins for room, apartment, building
	 */
	public async getAllOptimized(options?: any, meta?: { user?: any }) {
		const limit = Math.min(options?.pageSize || 20, 100);
		const offset = ((options?.page || 1) - 1) * limit || 0;

		const order: any[] = [];
		if (options?.sortField) {
			order.push([options.sortField, options?.sortOrder || 'DESC']);
		} else {
			order.push(['created_at', 'DESC']);
		}

		// Determine user context (meta takes precedence)
		const user = meta?.user ?? options?.user ?? null;
		const isAdmin = user?.roles?.some((r: string) => ['admin', 'system_admin'].includes(r)) ?? false;
		const filters = { ...(options?.filters || {}) } as any;
		if (!isAdmin) {
			const residentId = user?.resident_id ?? null;
			if (!residentId) {
				return {
					count: 0,
					rows: [],
					page: options?.page || 1,
					pageSize: limit,
				};
			}
			filters.resident_id = residentId;
		}

		try {
			const result = await Bill.findAndCountAll({
				where: {
					deleted_at: null,
					...(filters || {}),
				},
				include: [
					{
						model: Room,
						as: 'room',
						attributes: [],
						required: false,
						where: { deleted_at: null },
						include: [
							{
								model: Apartment,
								as: 'apartment',
								attributes: [],
								required: false,
								where: { deleted_at: null },
								include: [
									{
										model: Building,
										as: 'building',
										attributes: [],
										required: false,
										where: { deleted_at: null },
									},
								],
							},
						],
					},
					{
						model: Resident,
						as: 'resident',
						attributes: [],
						required: false,
						where: { deleted_at: null },
					},
				],
				attributes: [
					'id',
					'code',
					'room_id',
					'resident_id',
					'billing_period',
					'due_date',
					'payment_method',
					'notes',
					'total_amount',
					'status',
					'created_by',
					'created_at',
					'updated_at',
					'deleted_by',
					'deleted_at',
					[sequelize.col('room.name'), 'room_name'],
					[sequelize.col('room.apartment.name'), 'apartment_name'],
					[sequelize.col('room.apartment.building.name'), 'building_name'],
					[sequelize.col('resident.full_name'), 'resident_full_name'],
					[sequelize.col('resident.email'), 'resident_email'],
					[sequelize.col('resident.phone'), 'resident_phone'],
				],
				raw: true,
				subQuery: false,
				limit,
				offset,
				order,
				distinct: true,
			});

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

	/**
	 * Get bill by ID with bill_items
	 */
	public async getByIdOptimized(id: string) {
		try {
			const bill = await Bill.findOne({
				where: { id, deleted_at: null },
				include: [
					{
						model: BillItem,
						as: 'bill_items',
					},
					{
						model: Room,
						as: 'room',
						attributes: [],
						required: false,
						where: { deleted_at: null },
						include: [
							{
								model: Apartment,
								as: 'apartment',
								attributes: [],
								required: false,
								where: { deleted_at: null },
								include: [
									{
										model: Building,
										as: 'building',
										attributes: [],
										required: false,
										where: { deleted_at: null },
									},
								],
							},
						],
					},
					{
						model: Resident,
						as: 'resident',
						attributes: [],
						required: false,
						where: { deleted_at: null },
					},
				],
				attributes: [
					'id',
					'code',
					'room_id',
					'resident_id',
					'billing_period',
					'due_date',
					'payment_method',
					'notes',
					'total_amount',
					'status',
					'created_by',
					'created_at',
					'updated_at',
					'deleted_by',
					'deleted_at',
					[sequelize.col('room.name'), 'room_name'],
					[sequelize.col('room.apartment.name'), 'apartment_name'],
					[sequelize.col('room.apartment.building.name'), 'building_name'],
					[sequelize.col('resident.full_name'), 'resident_full_name'],
					[sequelize.col('resident.email'), 'resident_email'],
					[sequelize.col('resident.phone'), 'resident_phone'],
				],
			});

			if (!bill) {
				return null;
			}

			return bill.toJSON();
		} catch (error) {
			await this.logError(error as Error);
			throw error;
		}
	}
}
