import { PromotionDao } from "../dao/promotion.dao";
import { LanguageService } from "./language.service";
import { sequelize } from "../models";
import { Common } from "../../utils/common";
import { AppError } from "../../utils/errors";
import { AttachmentService } from "./attachment.service";

export class PromotionService {
    private accountId: number | null;
    private userId: number | null;
    private language: string;
    private scope: string[] | null;
    private config: userConfig | null;
    private promotionDao: PromotionDao;
    private languageService: LanguageService;
    private attachmentService: AttachmentService;
    constructor(
        private options: {
            language: string;
            scope: string[] | null;
            accountId: number | null;
            userId: number | null;
            config: userConfig | null;
        } = {
            language: process.env.DEFAULT_LANGUAGE_CODE!,
            scope: null,
            config: null,
            userId: null,
            accountId: null,
        }
    ) {
        this.language = options.language;
        this.scope = options.scope ?? [];
        this.config = options.config ?? null;
        this.userId = options.userId ?? null;
        this.accountId = options.accountId ?? null;
        this.promotionDao = new PromotionDao({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config,
        });

        this.languageService = new LanguageService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config,
        });

        this.attachmentService = new AttachmentService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config,
        });
    }

    // create a new promotion
    createPromotion = async (data: PromotionCreateServiceInput): Promise<PromotionObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { title, description, imageId = null, link, type, publishAt, expireAt, isPremium, status } = data;
            const languages: LanguageInfo = await this.languageService.getRequestedAndDefaultLanguage();
            let promotionObj: PromotionObject = {
                code: "-",
                status: status,
                imageId: imageId,
                link,
                type,
                publishAt,
                expireAt,
                isPremium,
            };
            let promotionContentObj: PromotionContentObject = { title, description };
            let code = Common.slugify(promotionContentObj.title);
            code = `${code}-${this.accountId}`;
            promotionContentObj.descriptionText = (promotionContentObj.description
                ? await Common.convertHtmlToText(promotionContentObj.description.toString())
                : null) as unknown as Text;
            const doesPromotionExistInput: PromotionDoExistsByCodeDaoInput = { code };
            const exists = await this.promotionDao.doExistsByCode(doesPromotionExistInput, { transaction });
            if (!exists) {
                promotionObj.code = code;
                if (promotionObj.imageId) {
                    const emailTemplateAttachments = [promotionObj.imageId];
                    const verifyAttachments = await this.attachmentService.verifyFile(emailTemplateAttachments);
                    if (verifyAttachments.length != emailTemplateAttachments.length) {
                        throw new AppError(400, "INVALID_DUPLICATE_ATTACHMENT_FOUND", {
                            emailTemplateAttachments: "INVALID_DUPLICATE_ATTACHMENT_FOUND",
                        });
                    }
                }
                const createPromotionInput: PromotionCreateDaoInput = { promotionObj, promotionContentObj, languages };
                let promotionIdentifier: number = await this.promotionDao.create(createPromotionInput, { transaction });
                const setSortOrderInput: PromotionSetSortOrderDaoInput = { id: promotionIdentifier };
                await this.promotionDao.setSortOrder(setSortOrderInput, { transaction });
                const getPromotionByIdInput: PromotionGetByIdDaoInput = { id: promotionIdentifier };
                let promotion: PromotionObjectInteface = await this.promotionDao.getPromotionById(getPromotionByIdInput, {
                    transaction,
                });
                await transaction.commit();
                return promotion;
            }
            throw new AppError(400, "PROMOTIONS_ALREADY_EXISTS", { title: "PROMOTIONS_ALREADY_EXISTS" });
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // update the existing promotion
    updatePromotion = async (data: PromotionUpdateServiceInput): Promise<PromotionObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { id, title, description, imageId = null, link, type, publishAt, expireAt, isPremium, status } = data;
            const languages: LanguageInfo = await this.languageService.getRequestedLanguage();
            let promotionObj: PromotionObject = {
                code: "-",
                status: status,
                imageId: imageId,
                link,
                type,
                publishAt,
                expireAt,
                isPremium,
            };
            let promotionContentObj: PromotionContentObject = { title, description };
            let code = languages.requested.code == process.env.DEFAULT_LANGUAGE_CODE ? (promotionObj.code = Common.slugify(promotionContentObj.title)) : null;
            code = `${code}-${this.accountId}`;
            promotionContentObj.descriptionText = (promotionContentObj.description
                ? await Common.convertHtmlToText(promotionContentObj.description.toString())
                : null) as unknown as Text;
            const doesPromotionExistInput: PromotionDoExistsByIdDaoInput = { id };
            const exists = await this.promotionDao.doExistsById(doesPromotionExistInput, { transaction });
            if (exists) {
                promotionObj.code = code;
                if (promotionObj.code) {
                    const codeInUseInput: PromotionDoExistsByCodeDaoInput = { code: promotionObj.code, excludeId: id };
                    let codeInUse = await this.promotionDao.doExistsByCode(codeInUseInput, { transaction });
                    if (codeInUse) {
                        throw new AppError(400, "PROMOTIONS_ALREADY_EXISTS", { title: "PROMOTIONS_ALREADY_EXISTS" });
                    }
                }
                if (promotionObj.imageId) {
                    const emailTemplateAttachments = [promotionObj.imageId];
                    const verifyAttachments = await this.attachmentService.verifyFile(emailTemplateAttachments);
                    if (verifyAttachments.length != emailTemplateAttachments.length) {
                        throw new AppError(400, "INVALID_DUPLICATE_ATTACHMENT_FOUND", {
                            emailTemplateAttachments: "INVALID_DUPLICATE_ATTACHMENT_FOUND",
                        });
                    }
                }
                const updatePromotionInput: PromotionUpdateDaoInput = { id, promotionObj, promotionContentObj, languages };
                await this.promotionDao.update(updatePromotionInput, { transaction });
                const getPromotionByIdInput: PromotionGetByIdDaoInput = { id };
                let promotion: PromotionObjectInteface = await this.promotionDao.getPromotionById(getPromotionByIdInput, {
                    transaction,
                });
                await transaction.commit();
                return promotion;
            }
            throw new AppError(404, "PROMOTIONS_NOT_FOUND", { title: "PROMOTIONS_NOT_FOUND" });
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // delete the existing promotion
    deletePromotion = async (data: PromotionDeleteServiceInput): Promise<PromotionObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const deletePromotionInput: PromotionDeleteDaoInput = { id: data.id };
            await this.promotionDao.delete(deletePromotionInput, { transaction });
            const getPromotionByIdInput: PromotionGetByIdDaoInput = { id: data.id, expanded: true, paranoid: false };
            let promotion: PromotionObjectInteface = await this.promotionDao.getPromotionById(getPromotionByIdInput, { transaction });
            await transaction.commit();
            return promotion;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // get promotion by id
    getById = async (data: PromotionGetByIdServiceInput): Promise<PromotionObjectInteface> => {
        try {
            const getPromotionByIdInput: PromotionGetByIdDaoInput = {
                id: data.id,
                expanded: data.expanded ?? true,
            };
            let promotion: PromotionObjectInteface = await this.promotionDao.getPromotionById(getPromotionByIdInput);
            return promotion;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // get promotion by code
    getByCode = async (data: PromotionGetByCodeServiceInput): Promise<PromotionObjectInteface> => {
        try {
            const getPromotionByCodeInput: PromotionGetByCodeDaoInput = {
                code: data.code,
                expanded: data.expanded ?? true,
            };
            let promotion: PromotionObjectInteface = await this.promotionDao.getPromotionByCode(getPromotionByCodeInput);
            return promotion;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // list promotion
    getPromotions = async (data: PromotionGetPromotionsServiceInput): Promise<PromotionPaginatedList> => {
        try {
            const { type, listRequest, status = null } = data;
            const { page, perPage } = listRequest;
            if (status) {
                listRequest.status = status;
            }
            const getPromotionListInput: PromotionGetPromotionListDaoInput = { type, listRequest };
            let promotions: PromotionPaginatedData = await this.promotionDao.getPromotionList(getPromotionListInput);
            let totalPages = Common.getTotalPages(promotions.count, perPage);
            return {
                data: promotions.rows,
                page: page,
                perPage: perPage,
                totalRecords: promotions.count,
                totalPages: totalPages,
            } as unknown as PromotionPaginatedList;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // list all promotion
    getAllPromotions = async (data: PromotionGetAllPromotionsServiceInput): Promise<PromotionObjectSummaryInterface[]> => {
        try {
            const getAllPromotionsInput: PromotionGetAllPromotionsDaoInput = { type: data.type, listRequest: data.listRequest };
            let promotions: PromotionObjectSummaryInterface[] = await this.promotionDao.getAllPromotions(getAllPromotionsInput);
            return promotions;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // list promotion revisions
    getPromotionsRevisions = async (data: PromotionGetPromotionsRevisionsServiceInput): Promise<PromotionPaginatedList> => {
        try {
            const { id, listRequest, language = process.env.DEFAULT_LANGUAGE_CODE! } = data;
            const { page, perPage } = listRequest;
            const getPromotionRevisionListInput: PromotionGetPromotionRevisionListDaoInput = { id, listRequest, language };
            let promotions: PromotionPaginatedData = await this.promotionDao.getPromotionRevisionList(getPromotionRevisionListInput);
            let totalPages = Common.getTotalPages(promotions.count, perPage);
            return {
                data: promotions.rows,
                page: page,
                perPage: perPage,
                totalRecords: promotions.count,
                totalPages: totalPages,
            } as unknown as PromotionPaginatedList;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // Restore promotion revision
    restoreRevision = async (data: PromotionRestoreRevisionServiceInput): Promise<PromotionObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const restorePromotionRevisionInput: PromotionRestoreRevisionDaoInput = { id: data.id };
            let restoredEntiryId: number = await this.promotionDao.restoreRevision(restorePromotionRevisionInput, { transaction });
            const getPromotionByIdInput: PromotionGetByIdDaoInput = { id: restoredEntiryId };
            let promotion = await this.promotionDao.getPromotionById(getPromotionByIdInput, { transaction });
            await transaction.commit();
            return promotion;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // get promotion id from code
    getPromotionId = async (data: PromotionGetPromotionIdServiceInput): Promise<number> => {
        try {
            const getPromotionIdInput: PromotionGetIdFromCodeDaoInput = { code: data.code };
            let promotionId = await this.promotionDao.getIdFromCode(getPromotionIdInput);
            return promotionId;
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // update promotionId sort order
    setSortOrder = async (data: PromotionSetSortOrderServiceInput): Promise<boolean> => {
        const transaction = await sequelize.transaction();
        try {
            const setSortOrderInput: PromotionSetSortOrderDaoInput = {
                id: data.id,
                before: data.before,
                after: data.after,
            };
            let sortOrder: boolean = await this.promotionDao.setSortOrder(setSortOrderInput, { transaction });
            await transaction.commit();
            return sortOrder;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };

    // update questionnaire status
    updateStatus = async (data: PromotionUpdateStatusServiceInput): Promise<PromotionObjectInteface> => {
        try {
            const getPromotionByIdInput: PromotionGetByIdDaoInput = { id: data.id };
            let promotion = await this.promotionDao.getPromotionById(getPromotionByIdInput);
            if (promotion) {
                const updatePromotionStatusInput: PromotionUpdateStatusDaoInput = { id: data.id, status: data.status };
                await this.promotionDao.updateStatus(updatePromotionStatusInput);
                let promotionObject = await this.promotionDao.getPromotionById(getPromotionByIdInput);
                return promotionObject;
            } else {
                throw new AppError(404, "PROMOTIONS_NOT_FOUND", { id: "PROMOTIONS_NOT_FOUND" });
            }
        } catch (err) {
            if (err instanceof AppError) {
                throw err;
            }
            throw new AppError(500, "SOMETHING_WENT_WRONG_IN_SERVICE", err);
        }
    };
}
