// RoomServicesModel.ts
import { Room, Service, Status } from "entity";
import { EntityManager, In, SelectQueryBuilder } from "typeorm";
import CommonHelper from "../helpers/common";
import { OrmHelper } from "../helpers/orm";
import { ServiceModel } from "./service";

export class RoomServicesModel {
    static async listByService(filter = {}, withDeleted = false): Promise<SelectQueryBuilder<Service>> {
        const repo = OrmHelper.DB.getRepository(Service);
        let whereAttr: string[] = [];
        let whereVal: any = {};

        if (filter && Object.keys(filter).length > 0) {
            const filterResult = CommonHelper.handleQueryFilter(filter);
            whereAttr = [...whereAttr, ...filterResult.whereAttr];
            whereVal = { ...whereVal, ...filterResult.whereVal };
        }

        let query = repo.createQueryBuilder("Service")
            .leftJoinAndSelect("Service.rooms", "Room")
            .leftJoinAndSelect("Room.department", "Department")
            .leftJoinAndSelect("Service.serviceType", "ServiceType");

        if (whereAttr.length != 0) {
            query = query.where(whereAttr.join(" and "), whereVal);
        }

        if (withDeleted) {
            query = query.withDeleted();
        }

        return query;
    }

    static async listByRoom(filter = {}, withDeleted = false): Promise<SelectQueryBuilder<Room>> {
        const repo = OrmHelper.DB.getRepository(Room);
        let whereAttr: string[] = [];
        let whereVal: any = {};

        if (filter && Object.keys(filter).length > 0) {
            const filterResult = CommonHelper.handleQueryFilter(filter, "Room");
            whereAttr = [...whereAttr, ...filterResult.whereAttr];
            whereVal = { ...whereVal, ...filterResult.whereVal };
        }

        let query = repo.createQueryBuilder("Room")
            .leftJoinAndSelect("Room.services", "Service")
            .leftJoinAndSelect("Room.department", "Department")
            .leftJoinAndSelect("Service.serviceType", "ServiceType");

        if (whereAttr.length != 0) {
            query = query.where(whereAttr.join(" and "), whereVal);
        }

        if (withDeleted) {
            query = query.withDeleted();
        }

        return query;
    }

    static async countRooms(filter = {}, withDeleted = false): Promise<number> {
        const repo = OrmHelper.DB.getRepository(Service);

        let queryBuilder = repo.createQueryBuilder("Service")
            .innerJoin("Service.rooms", "Room")
            .select("COUNT(DISTINCT Room.id)", "count");

        if (filter && Object.keys(filter).length > 0) {
            const filterResult = CommonHelper.handleQueryFilter(filter);
            if (filterResult.whereAttr.length > 0) {
                queryBuilder = queryBuilder.where(filterResult.whereAttr.join(" and "), filterResult.whereVal);
            }
        }

        if (withDeleted) {
            queryBuilder = queryBuilder.withDeleted();
        }

        const result = await queryBuilder.getRawOne();
        return parseInt(result.count) || 0;
    }

    static async countServices(filter = {}, withDeleted = false): Promise<number> {
        const repo = OrmHelper.DB.getRepository(Room);

        let queryBuilder = repo.createQueryBuilder("Room")
            .innerJoin("Room.services", "Service")
            .select("COUNT(DISTINCT Service.id)", "count");

        if (filter && Object.keys(filter).length > 0) {
            const filterResult = CommonHelper.handleQueryFilter(filter, "Room");
            if (filterResult.whereAttr.length > 0) {
                queryBuilder = queryBuilder.where(filterResult.whereAttr.join(" and "), filterResult.whereVal);
            }
        }

        if (withDeleted) {
            queryBuilder = queryBuilder.withDeleted();
        }

        const result = await queryBuilder.getRawOne();
        return parseInt(result.count) || 0;
    }

    static async expandTypeIdsToServices(
        manager: EntityManager,
        serviceTypeIds: string[]
    ): Promise<Service[]> {
        if (!serviceTypeIds || serviceTypeIds.length === 0) {
            return [];
        }

        return manager.getRepository(Service).find({
            where: {
                serviceType: { id: In(serviceTypeIds) },
                status: Status.Active,
            },
            relations: ["serviceType"],
        });
    }

    static async getActiveServicesGroupedByType(manager: EntityManager = OrmHelper.DB.manager) {
        const services = await manager.getRepository(Service).find({
            where: { status: Status.Active },
            relations: ["serviceType"],
        });

        const byTypeId = new Map<string, string[]>();
        const typeMeta = new Map<string, { id: string; name: string }>();

        for (const service of services) {
            const typeId = service.serviceType?.id;
            if (!typeId) {
                continue;
            }

            if (!byTypeId.has(typeId)) {
                byTypeId.set(typeId, []);
            }
            byTypeId.get(typeId)!.push(service.id);

            if (!typeMeta.has(typeId)) {
                typeMeta.set(typeId, { id: typeId, name: service.serviceType!.name });
            }
        }

        return { byTypeId, typeMeta };
    }

    static deriveTypesForRoomWithMap(
        room: Room,
        byTypeId: Map<string, string[]>,
        typeMeta: Map<string, { id: string; name: string }>
    ): { id: string; name: string }[] {
        const roomServiceIds = new Set((room.services || []).map((s) => s.id));
        const derived: { id: string; name: string }[] = [];

        for (const [typeId, serviceIds] of byTypeId) {
            if (serviceIds.length === 0) {
                continue;
            }

            const allInRoom = serviceIds.every((id) => roomServiceIds.has(id));
            if (allInRoom) {
                const meta = typeMeta.get(typeId);
                if (meta) {
                    derived.push(meta);
                }
            }
        }

        return derived.sort((a, b) => a.name.localeCompare(b.name));
    }

    static async deriveTypesForRoom(
        room: Room,
        manager: EntityManager = OrmHelper.DB.manager
    ): Promise<{ id: string; name: string }[]> {
        const { byTypeId, typeMeta } = await RoomServicesModel.getActiveServicesGroupedByType(manager);
        return RoomServicesModel.deriveTypesForRoomWithMap(room, byTypeId, typeMeta);
    }

    static async formatRoomResponse(
        room: Room,
        manager: EntityManager = OrmHelper.DB.manager
    ) {
        const service_types = await RoomServicesModel.deriveTypesForRoom(room, manager);

        return {
            id: room.id,
            room: room.room,
            code: room.code,
            description: room.description,
            status: room.status,
            department: room.department
                ? {
                      id: room.department.id,
                      name: room.department.name,
                  }
                : null,
            service_types,
            services: room.services
                ? room.services.map((service) => ({
                      id: service.id,
                      name: service.name,
                      service_type: service.serviceType
                          ? {
                                id: service.serviceType.id,
                                name: service.serviceType.name,
                            }
                          : null,
                  }))
                : [],
        };
    }

    static async applyTypesToRoom(
        manager: EntityManager,
        room: Room,
        serviceTypeIds: string[],
        updatedBy: string
    ): Promise<Room> {
        const services = await RoomServicesModel.expandTypeIdsToServices(manager, serviceTypeIds);
        room.services = services;
        room.updated_by = updatedBy;
        return manager.save(room);
    }

    static async removeServiceFromAllRooms(manager: EntityManager, serviceId: string): Promise<void> {
        await manager
            .createQueryBuilder()
            .delete()
            .from("service_rooms")
            .where("service_id = :serviceId", { serviceId })
            .execute();
    }

    static async syncRoomsOnServiceCreated(
        manager: EntityManager,
        service: Service
    ): Promise<void> {
        const typeId = service.serviceType?.id;
        if (!typeId) {
            return;
        }

        const othersOfType = await manager.getRepository(Service).find({
            where: {
                serviceType: { id: typeId },
                status: Status.Active,
            },
        });

        const otherIds = othersOfType.filter((s) => s.id !== service.id).map((s) => s.id);
        if (otherIds.length === 0) {
            return;
        }

        const rooms = await manager.getRepository(Room).find({
            relations: ["services"],
        });

        for (const room of rooms) {
            const roomIds = new Set((room.services || []).map((s) => s.id));
            const hadFullType = otherIds.every((id) => roomIds.has(id));
            if (!hadFullType) {
                continue;
            }
            if (roomIds.has(service.id)) {
                continue;
            }
            room.services = [...(room.services || []), service];
            await manager.save(room);
        }
    }

    static async syncRoomsOnServiceUpdated(
        manager: EntityManager,
        service: Service,
        oldTypeId?: string
    ): Promise<void> {
        const newTypeId = service.serviceType?.id;
        const typeChanged = oldTypeId !== newTypeId;

        if (service.status !== Status.Active) {
            await RoomServicesModel.removeServiceFromAllRooms(manager, service.id);
            return;
        }

        if (!typeChanged) {
            return;
        }

        await RoomServicesModel.removeServiceFromAllRooms(manager, service.id);

        if (!newTypeId) {
            return;
        }

        const reloaded = await ServiceModel.list({ "Service.id": service.id })
            .then((q) => q.leftJoinAndSelect("Service.serviceType", "serviceType").getOne());

        if (reloaded) {
            await RoomServicesModel.syncRoomsOnServiceCreated(manager, reloaded);
        }
    }

    static async syncRoomsOnServiceDeleted(
        manager: EntityManager,
        serviceId: string
    ): Promise<void> {
        await RoomServicesModel.removeServiceFromAllRooms(manager, serviceId);
    }
}
