import { DoctorItem, GenericName, ItemMaster, ItemPrice, ItemTypeDetail, User, SellingPricePercentage, PharmacyInfo, RoomToPharmacy } from "entity";
import { NextFunction, Response } from "express";
import { Request } from "express-jwt";
import Joi from "joi";
import { ILogObj, Logger } from "tslog";
import { ReturnHelper } from "../helpers/express/return";
import { Language } from "../langs/lang";
import { DoctorItemModel } from "../model/doctor_item";
import { OrmHelper } from "../helpers/orm";
import CommonHelper from "../helpers/common";

const log: Logger<ILogObj> = new Logger({
    name: "[DoctorItemController]",
    type: "pretty",
});

export class DoctorItemController {
    static async listAll(req: Request, res: Response, next: NextFunction): Promise<void | Response> {
        /*
            #swagger.tags = ['Doctor Item']
            #swagger.security = [{ "bearerAuth": [] }]
            #swagger.parameters['doctor_id'] = { in: 'query', required: true, type: 'string' }
            #swagger.parameters['room_id'] = { in: 'query', required: true, type: 'string' }
            #swagger.parameters['filter'] = { description: '', in: 'query', type: 'string' }
            #swagger.parameters['page'] = { in: 'query', required: true, type: 'number' }
            #swagger.parameters['limit'] = { in: 'query', required: true, type: 'number' }
            #swagger.parameters['order_field'] = { in: 'query', required: false, type: 'string' }
            #swagger.parameters['order_direction'] = { in: 'query', required: false, schema: { '@enum': ['ASC', 'DESC'] } }
        */
        try {
            const schema = Joi.object().keys({
                doctor_id: Joi.string().uuid().required().label("Doctor ID"),
                room_id: Joi.string().uuid().required().label("Room ID"),
                filter: Joi.string().allow("").optional().label("Filter"),
                page: Joi.number().required().min(1).label("Page"),
                limit: Joi.number().required().min(1).label("Limit"),
                order_field: Joi.string().optional().label("Order Field"),
                order_direction: Joi.string().allow("asc", "desc").optional().label("Order Direction"),
            });

            let param: any = await schema.validateAsync(req.query);

            let filter = param.filter && param.filter !== "" ? JSON.parse(param.filter) : {};
            let filterAny = filter.any && filter.any !== "" ? filter.any : "";

            let filterObj = { ...filter } as any;
            delete filterObj.any;
            // filterObj = { ...filterObj};

            const repo = OrmHelper.DB.getRepository(ItemMaster);

            let whereAttr: string[] = [];
            let whereVal: any = {};

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

            if (filterAny && filterAny !== "" && filterAny !== null && filterAny !== undefined) {
                const filterResult = CommonHelper.handleFilter({
                    filter: JSON.stringify({ any: filterAny.toLowerCase() }),
                    col_any_eq: [],
                    col_any_like: ["item_name", "generic_name"],
                });

                if (filterResult.whereAttr && filterResult.whereAttr !== "") {
                    whereAttr = [...whereAttr, filterResult.whereAttr];
                    whereVal = { ...whereVal, ...filterResult.whereVal };
                }
            }

            const orderField = param.order_field || "id";
            const orderDirection = param.order_direction || "asc";

            const offset = (param.page - 1) * param.limit;

            const pharmacyInfoRow = await OrmHelper.DB
                .getRepository(PharmacyInfo)
                .createQueryBuilder("pharmacy_info")
                .select("pharmacy_info.default_room_id", "default_room_id")
                .where("pharmacy_info.deleted_at IS NULL")
                .orderBy("pharmacy_info.created_at", "DESC")
                .limit(1)
                .getRawOne<{ default_room_id: string | null }>();

            const defaultRoomId = pharmacyInfoRow?.default_room_id ?? null;

            const roomToPharmacyRow = await OrmHelper.DB
                .getRepository(RoomToPharmacy)
                .createQueryBuilder("room_to_pharmacy")
                .select("room_to_pharmacy.pharmacy_room_id", "pharmacy_room_id")
                .where("room_to_pharmacy.deleted_at IS NULL")
                .andWhere("room_to_pharmacy.room_id = :roomId", { roomId: param.room_id })
                .orderBy("room_to_pharmacy.created_at", "DESC")
                .limit(1)
                .getRawOne<{ pharmacy_room_id: string | null }>();

            const pharmacyRoomId = roomToPharmacyRow?.pharmacy_room_id ?? null;

            let res_count = repo.createQueryBuilder("item_master")
                .leftJoin("item_master.generic_name", "generic_name")
                .leftJoin("item_master.type_detail", "item_type_detail")
                .leftJoin(ItemPrice, "item_price", "item_price.item_master_id = item_master.id")
                .leftJoin(SellingPricePercentage, "selling_price_percentage", "selling_price_percentage.item_master_id = item_master.id")
                .leftJoin(
                    DoctorItem,
                    "doctor_item",
                    "doctor_item.item_master_id = item_master.id AND doctor_item.doctor_id = :doctorId",
                    { doctorId: param.doctor_id }
                );

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

            let res_list = repo.createQueryBuilder("item_master")
                .leftJoin("item_master.generic_name", "generic_name")
                .leftJoin("item_master.type_detail", "item_type_detail")
                .leftJoin(ItemPrice, "item_price", "item_price.item_master_id = item_master.id")
                .leftJoin(SellingPricePercentage, "selling_price_percentage", "selling_price_percentage.item_master_id = item_master.id")
                .leftJoin(
                    DoctorItem,
                    "doctor_item",
                    "doctor_item.item_master_id = item_master.id AND doctor_item.doctor_id = :doctorId",
                    { doctorId: param.doctor_id }
                );

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

            res_list = res_list
                .select([
                    "item_master.active_ingredient as active_ingredient",
                    "item_master.id as item_id",
                    "item_master.item_name as item_name",
                    "generic_name.generic_name as item_generic_name",
                    "item_type_detail.type_detail_name as item_type",
                    (pharmacyRoomId
                        ? `COALESCE((SELECT SUM(rs.total_stock)::text FROM pharmacy_logistic.room_stock rs WHERE rs."roomId" = :pharmacyRoomId AND rs."itemmasterId" = item_master.id AND rs.deleted_at IS NULL), '0') as pharmacy_stock`
                        : "'0' as pharmacy_stock"),
                    (defaultRoomId
                        ? `COALESCE((SELECT SUM(rs.total_stock)::text FROM pharmacy_logistic.room_stock rs WHERE rs."roomId" = :defaultRoomId AND rs."itemmasterId" = item_master.id AND rs.deleted_at IS NULL), '0') as storage_stock`
                        : "'0' as storage_stock"),
                    "ROUND(CASE WHEN selling_price_percentage.percentage IS NULL " +
                    "THEN COALESCE(item_price.selling_price, 0) " +
                    "ELSE COALESCE(item_price.selling_price, 0) + (COALESCE(item_price.selling_price, 0) * COALESCE(selling_price_percentage.percentage, 0) / 100) END, 2) as price",
                    "doctor_item.id as fav_id",
                ])
                .orderBy("item_master." + orderField, orderDirection)
                .offset(offset)
                .limit(param.limit)

            if (defaultRoomId) {
                res_list = res_list.setParameter("defaultRoomId", defaultRoomId);
            }
            if (pharmacyRoomId) {
                res_list = res_list.setParameter("pharmacyRoomId", pharmacyRoomId);
            }

            const current_page = param.page;
            const total_count_data = await res_count.getCount();
            const list_data = await res_list.getRawMany();
            const count_data = CommonHelper.countObject(list_data);

            return ReturnHelper.successResponselist(res, 200, Language.lang.success_view, count_data, current_page, total_count_data, list_data);
        } catch (e: unknown) {
            log.error(e);
            const err = e as Error;
            return ReturnHelper.errorResponse(res, 400, 401, Language.lang.failed_view, err.message);
        }
    }

    static async listFav(req: Request, res: Response, next: NextFunction): Promise<void | Response> {
        /*
            #swagger.tags = ['Doctor Item']
            #swagger.security = [{ "bearerAuth": [] }]
            #swagger.parameters['doctor_id'] = { in: 'query', required: true, type: 'string' }
            #swagger.parameters['room_id'] = { in: 'query', required: true, type: 'string' }
            #swagger.parameters['filter'] = { description: '', in: 'query', type: 'string' }
            #swagger.parameters['page'] = { in: 'query', required: true, type: 'number' }
            #swagger.parameters['limit'] = { in: 'query', required: true, type: 'number' }
            #swagger.parameters['order_field'] = { in: 'query', required: false, type: 'string' }
            #swagger.parameters['order_direction'] = { in: 'query', required: false, schema: { '@enum': ['ASC', 'DESC'] } }
        */
        try {
            const schema = Joi.object().keys({
                doctor_id: Joi.string().uuid().required().label("Doctor ID"),
                room_id: Joi.string().uuid().required().label("Room ID"),
                filter: Joi.string().allow("").optional().label("Filter"),
                page: Joi.number().required().min(1).label("Page"),
                limit: Joi.number().required().min(1).label("Limit"),
                order_field: Joi.string().optional().label("Order Field"),
                order_direction: Joi.string().allow("asc", "desc").optional().label("Order Direction"),
            });

            let param: any = await schema.validateAsync(req.query);

            let filter = param.filter && param.filter !== "" ? JSON.parse(param.filter) : {};
            let filterAny = filter.any && filter.any !== "" ? filter.any : "";

            let filterObj = { ...filter } as any;
            delete filterObj.any;
            // filterObj = { ...filterObj};

            const repo = OrmHelper.DB.getRepository(ItemMaster);

            let whereAttr: string[] = [];
            let whereVal: any = {};

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

            if (filterAny && filterAny !== "" && filterAny !== null && filterAny !== undefined) {
                const filterResult = CommonHelper.handleFilter({
                    filter: JSON.stringify({ any: filterAny.toLowerCase() }),
                    col_any_eq: [],
                    col_any_like: ["item_name", "generic_name"],
                });

                if (filterResult.whereAttr && filterResult.whereAttr !== "") {
                    whereAttr = [...whereAttr, filterResult.whereAttr];
                    whereVal = { ...whereVal, ...filterResult.whereVal };
                }
            }

            const orderField = param.order_field || "id";
            const orderDirection = param.order_direction || "asc";

            const offset = (param.page - 1) * param.limit;

            const pharmacyInfoRow = await OrmHelper.DB
                .getRepository(PharmacyInfo)
                .createQueryBuilder("pharmacy_info")
                .select("pharmacy_info.default_room_id", "default_room_id")
                .where("pharmacy_info.deleted_at IS NULL")
                .orderBy("pharmacy_info.created_at", "DESC")
                .limit(1)
                .getRawOne<{ default_room_id: string | null }>();

            const defaultRoomId = pharmacyInfoRow?.default_room_id ?? null;

            const roomToPharmacyRow = await OrmHelper.DB
                .getRepository(RoomToPharmacy)
                .createQueryBuilder("room_to_pharmacy")
                .select("room_to_pharmacy.pharmacy_room_id", "pharmacy_room_id")
                .where("room_to_pharmacy.deleted_at IS NULL")
                .andWhere("room_to_pharmacy.room_id = :roomId", { roomId: param.room_id })
                .orderBy("room_to_pharmacy.created_at", "DESC")
                .limit(1)
                .getRawOne<{ pharmacy_room_id: string | null }>();

            const pharmacyRoomId = roomToPharmacyRow?.pharmacy_room_id ?? null;

            let res_count = repo.createQueryBuilder("item_master")
                .innerJoin(
                    DoctorItem,
                    "doctor_item",
                    "doctor_item.item_master_id = item_master.id AND doctor_item.doctor_id = :doctorId",
                    { doctorId: param.doctor_id }
                )
                .leftJoin("item_master.generic_name", "generic_name")
                .leftJoin("item_master.type_detail", "item_type_detail")
                .leftJoin(ItemPrice, "item_price", "item_price.item_master_id = item_master.id")
                .leftJoin(SellingPricePercentage, "selling_price_percentage", "selling_price_percentage.item_master_id = item_master.id");

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

            let res_list = repo.createQueryBuilder("item_master")
                .innerJoin(
                    DoctorItem,
                    "doctor_item",
                    "doctor_item.item_master_id = item_master.id AND doctor_item.doctor_id = :doctorId",
                    { doctorId: param.doctor_id }
                )
                .leftJoin("item_master.generic_name", "generic_name")
                .leftJoin("item_master.type_detail", "item_type_detail")
                .leftJoin(ItemPrice, "item_price", "item_price.item_master_id = item_master.id")
                .leftJoin(SellingPricePercentage, "selling_price_percentage", "selling_price_percentage.item_master_id = item_master.id");

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

            res_list = res_list
                .select([
                    "item_master.id as item_id",
                    "item_master.item_name as item_name",
                    "generic_name.generic_name as item_generic_name",
                    "item_type_detail.type_detail_name as item_type",
                    (pharmacyRoomId
                        ? `COALESCE((SELECT SUM(rs.total_stock)::text FROM pharmacy_logistic.room_stock rs WHERE rs."roomId" = :pharmacyRoomId AND rs."itemmasterId" = item_master.id AND rs.deleted_at IS NULL), '0') as pharmacy_stock`
                        : "'0' as pharmacy_stock"),
                    (defaultRoomId
                        ? `COALESCE((SELECT SUM(rs.total_stock)::text FROM pharmacy_logistic.room_stock rs WHERE rs."roomId" = :defaultRoomId AND rs."itemmasterId" = item_master.id AND rs.deleted_at IS NULL), '0') as storage_stock`
                        : "'0' as storage_stock"),
                    "ROUND(CASE WHEN selling_price_percentage.percentage IS NULL " +
                    "THEN COALESCE(item_price.selling_price, 0) " +
                    "ELSE COALESCE(item_price.selling_price, 0) + (COALESCE(item_price.selling_price, 0) * COALESCE(selling_price_percentage.percentage, 0) / 100) END, 2) as price",
                    "doctor_item.id as fav_id",
                ])
                .orderBy("item_master." + orderField, orderDirection)
                .offset(offset)
                .limit(param.limit);

            if (defaultRoomId) {
                res_list = res_list.setParameter("defaultRoomId", defaultRoomId);
            }
            if (pharmacyRoomId) {
                res_list = res_list.setParameter("pharmacyRoomId", pharmacyRoomId);
            }

            const current_page = param.page;
            const total_count_data = await res_count.getCount();
            const list_data = await res_list.getRawMany();
            const count_data = CommonHelper.countObject(list_data);

            return ReturnHelper.successResponselist(res, 200, Language.lang.success_view, count_data, current_page, total_count_data, list_data);
        } catch (e: unknown) {
            log.error(e);
            const err = e as Error;
            return ReturnHelper.errorResponse(res, 400, 401, Language.lang.failed_view, err.message);
        }
    }

    static async selectFav(req: Request, res: Response, next: NextFunction): Promise<void | Response> {
        /*
         #swagger.tags = ['Doctor Item']
         #swagger.security = [{ "bearerAuth": [] }]
         #swagger.requestBody = { required: true, content: { "application/json": { schema: { $ref: "#/components/schemas/doctorItem" } } } }
         */
        const queryRunner = OrmHelper.DB.createQueryRunner();
        await queryRunner.startTransaction();
        try {
            const schema = Joi.object().keys({
                doctor_id: Joi.string().uuid().required().label("Doctor ID"),
                item_master_id: Joi.string().uuid().required().label("Item Master ID"),
            });

            let param: any = await schema.validateAsync(req.body);

            const doctor = await OrmHelper.DB.getRepository(User).findOne({ where: { id: param.doctor_id } });
            if (!doctor) throw { message: "Doctor (User) " + Language.lang.failed_not_found };

            const itemMaster = await OrmHelper.DB.getRepository(ItemMaster).findOne({ where: { id: param.item_master_id } });
            if (!itemMaster) throw { message: "Item Master " + Language.lang.failed_not_found };

            const exist = await DoctorItemModel.list({ doctor_id: param.doctor_id, item_master_id: param.item_master_id }).then((q) => q.getOne());
            if (exist) throw { message: "Doctor Item " + Language.lang.failed_duplicate };

            let doctorItem = new DoctorItem();
            doctorItem.doctor = doctor;
            doctorItem.item_master = itemMaster;
            doctorItem.created_by = req.auth.data.name;
            doctorItem.updated_by = req.auth.data.name;
            await queryRunner.manager.save(doctorItem);

            await queryRunner.commitTransaction();

            return ReturnHelper.successResponseAny(res, 200, Language.lang.success_insert, doctorItem);
        } catch (e: unknown) {
            await queryRunner.rollbackTransaction();
            log.error(e);
            const err = e as Error;
            return ReturnHelper.errorResponse(res, 400, 401, Language.lang.failed_insert, err.message);
        } finally {
            await queryRunner.release();
        }
    }

    static async removeFav(req: Request, res: Response, next: NextFunction): Promise<Response> {
        /*
        #swagger.tags = ['Doctor Item']
        #swagger.security = [{ "bearerAuth": [] }]
        #swagger.parameters['fav_id'] = { in: 'path', required: true, type: 'string' }
        */
        try {
            const schema = Joi.object().keys({
                fav_id: Joi.string().uuid().required().label("Favorite ID"),
            });

            const param: { fav_id: string } = await schema.validateAsync(req.params);

            const repo = OrmHelper.DB.getRepository(DoctorItem);
            const affected = (await repo.delete({ id: param.fav_id })).affected ?? 0;

            if (affected > 0) {
                return ReturnHelper.successResponseAny(res, 200, Language.lang.success_delete, {});
            } else {
                return ReturnHelper.errorResponse(res, 404, 401, Language.lang.failed_not_found, "");
            }
        } catch (e: unknown) {
            log.error(e);
            const err = e as Error;
            return ReturnHelper.errorResponse(res, 500, 402, Language.lang.failed_delete, err.message);
        }
    }
}
