import { FaqDao } from "../dao/faq.dao";
import { LanguageService } from "../services/language.service";
import { CategoryService } from "../services/category.service";
import { sequelize } from "../models";
import { Common } from "../../utils/common";
import { AppError } from "../../utils/errors";
import { FAQ } from "../config/constants";

export class FaqService {
    private accountId: number | null;
    private userId: number | null;
    private language: string;
    private scope: string[] | null;
    private config: userConfig | null;
    private faqDao: FaqDao;
    private languageService: LanguageService;
    private categoryService: CategoryService;
    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.faqDao = new FaqDao({
            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.categoryService = new CategoryService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });

    }

    // revision settings
    public isRevisionEnabled: boolean = (process.env.REVISION_ENABLED !== 'true' ? true : false) || false

    // create a new content type
    createFaq = async (data: FaqCreateServiceInput): Promise<faqObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { question, answer, categoryCode, status } = data;
            const languages: LanguageInfo = await this.languageService.getRequestedAndDefaultLanguage();
            let categoryId = categoryCode ? await this.getCategoryId({ categoryCode }) : null;
            let faqObj: faqObject = { categoryId, status: status ?? FAQ.STATUS.ACTIVE, code: question };
            let faqContentObj: faqContentObject = { question, answer };
            const verifyCategoryIdInput: CategoryVerifyCategoryIdServiceInput = { categoryId: faqObj.categoryId as number, categoryTypeCode: 'faq' };
            let categoryVerification = faqObj.categoryId ? await this.categoryService.verifyCategoryId(verifyCategoryIdInput) : true;
            if (!categoryVerification) {
                throw new AppError(400, 'INVALID_FAQ_CATEGORY', { categoryId: 'INVALID_FAQ_CATEGORY' });
            }
            faqObj.code = Common.slugify(faqObj.code);
            faqContentObj.answerText = (faqContentObj.answer ? await Common.convertHtmlToText(faqContentObj.answer.toString()) : null) as unknown as Text;
            const doesFaqExistInput: FaqDoExistsByCodeDaoInput = { code: faqObj.code };
            const exists = await this.faqDao.doExistsByCode(doesFaqExistInput, { transaction });
            if (!exists) {
                const createFaqInput: FaqCreateDaoInput = { faqObj, faqContentObj, languages };
                let faqIdentifier: number = await this.faqDao.create(createFaqInput, { transaction });
                const setSortOrderInput: FaqSetSortOrderDaoInput = { id: faqIdentifier };
                await this.faqDao.setSortOrder(setSortOrderInput, { transaction });
                const getFaqByIdInput: FaqGetByIdDaoInput = { id: faqIdentifier };
                let faq: faqObjectInteface = await this.faqDao.getFaqById(getFaqByIdInput, { transaction });
                await transaction.commit();
                return faq;
            }
            throw new AppError(400, 'FAQ_ALREADY_EXISTS', { name: 'FAQ_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 content type
    updateFaq = async (data: FaqUpdateServiceInput): Promise<faqObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { id, question, answer, categoryCode, status } = data;
            const languages: LanguageInfo = await this.languageService.getRequestedLanguage();
            let categoryId = categoryCode ? await this.getCategoryId({ categoryCode }) : null;
            let faqObj: faqObject = { categoryId, status: status ?? FAQ.STATUS.ACTIVE, code: question };
            let faqContentObj: faqContentObject = { question, answer };
            const verifyCategoryIdInput: CategoryVerifyCategoryIdServiceInput = { categoryId: faqObj.categoryId as number, categoryTypeCode: 'faq' };
            let categoryVerification = faqObj.categoryId ? await this.categoryService.verifyCategoryId(verifyCategoryIdInput) : true;
            if (!categoryVerification) {
                throw new AppError(400, 'INVALID_FAQ_CATEGORY', { categoryId: 'INVALID_FAQ_CATEGORY' });
            }
            let code = (languages.requested.code == process.env.DEFAULT_LANGUAGE_CODE ? Common.slugify(faqObj.code) : null);
            if (code) {
                const codeInUseInput: FaqDoExistsByCodeDaoInput = { code, excludeId: id };
                let codeInUse = await this.faqDao.doExistsByCode(codeInUseInput, { transaction });
                if (codeInUse) {
                    throw new AppError(400, 'FAQ_ALREADY_EXISTS', { name: 'FAQ_ALREADY_EXISTS' });
                }
            }
            faqContentObj.answerText = (faqContentObj.answer ? await Common.convertHtmlToText(faqContentObj.answer.toString()) : null) as unknown as Text;
            const doesFaqExistInput: FaqDoExistsByIdDaoInput = { id };
            const exists = await this.faqDao.doExistsById(doesFaqExistInput, { transaction });
            if (exists) {
                const updateFaqInput: FaqUpdateDaoInput = { id, faqObj, faqContentObj, languages };
                await this.faqDao.update(updateFaqInput, { transaction });
                const getFaqByIdInput: FaqGetByIdDaoInput = { id };
                let faq = await this.faqDao.getFaqById(getFaqByIdInput, { transaction });
                await transaction.commit();
                return faq;
            }
            throw new AppError(404, 'FAQ_NOT_FOUND', { name: 'FAQ_NOT_FOUND' });
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // update the existing content type
    deleteFaq = async (data: FaqDeleteServiceInput): Promise<faqObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { id } = data;
            const doesFaqExistInput: FaqDoExistsByIdDaoInput = { id };
            const exists = await this.faqDao.doExistsById(doesFaqExistInput, { transaction });
            if (exists) {
                const deleteFaqInput: FaqDeleteDaoInput = { id };
                await this.faqDao.delete(deleteFaqInput, { transaction });
                const getFaqByIdInput: FaqGetByIdDaoInput = { id, paranoid: false };
                let faq = await this.faqDao.getFaqById(getFaqByIdInput, { transaction });
                await transaction.commit();
                return faq;
            }
            throw new AppError(404, 'FAQ_NOT_FOUND', { name: 'FAQ_NOT_FOUND' });
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get category Type by id
    getById = async (data: FaqGetByIdServiceInput): Promise<faqObjectInteface> => {
        try {
            const getFaqByIdInput: FaqGetByIdDaoInput = { id: data.id };
            let faq = await this.faqDao.getFaqById(getFaqByIdInput);
            if (faq) {
                return faq;
            }
            throw new AppError(404, 'FAQ_NOT_FOUND', { name: 'FAQ_NOT_FOUND' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get category Type by code
    getByCode = async (data: FaqGetByCodeServiceInput): Promise<faqObjectInteface> => {
        try {
            const getFaqByCodeInput: FaqGetByCodeDaoInput = { code: data.code };
            let faq = await this.faqDao.getFaqByCode(getFaqByCodeInput);
            if (faq) {
                return faq;
            }
            throw new AppError(404, 'FAQ_NOT_FOUND', { name: 'FAQ_NOT_FOUND' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list category types
    getFaqs = async (data: FaqGetFaqsServiceInput): Promise<FaqPaginatedList> => {
        try {
            const { listRequest, status = null } = data;
            const { page, perPage } = listRequest;
            if (status) {
                listRequest.status = status;
            }
            let categoryId = listRequest.categoryCode ? await this.getCategoryId({ categoryCode: listRequest.categoryCode }) : null;
            const getFaqListInput: FaqGetFaqListDaoInput = { categoryId, listRequest };
            let faqs: FaqPaginatedData = await this.faqDao.getFaqList(getFaqListInput);
            if (faqs) {
                let totalPages = Common.getTotalPages(faqs.count, perPage);
                return {
                    data: faqs.rows,
                    page: page,
                    perPage: perPage,
                    totalRecords: faqs.count,
                    totalPages: totalPages
                } as unknown as FaqPaginatedList;
            }
            throw new AppError(400, 'FAQ_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list all category types
    getAllFaqs = async (data: FaqGetAllFaqsServiceInput): Promise<faqObjectSummaryInteface[]> => {
        try {
            const { listRequest } = data;
            let categoryId = listRequest.categoryCode ? await this.getCategoryId({ categoryCode: listRequest.categoryCode }) : null;
            const getAllFaqsInput: FaqGetAllFaqsDaoInput = { categoryId, listRequest };
            let faqs = await this.faqDao.getAllFaqs(getAllFaqsInput);
            if (faqs) {
                return faqs;
            }
            throw new AppError(400, 'FAQ_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list category types revisions
    getFaqsRevisions = async (data: FaqGetFaqRevisionsServiceInput): Promise<FaqPaginatedList> => {
        try {
            const { id, listRequest } = data;
            const { page, perPage } = listRequest;
            const getFaqRevisionListInput: FaqGetFaqRevisionListDaoInput = { id, listRequest };
            let faqs: FaqPaginatedData = await this.faqDao.getFaqRevisionList(getFaqRevisionListInput);
            if (faqs) {
                let totalPages = Common.getTotalPages(faqs.count, perPage);
                return {
                    data: faqs.rows,
                    page: page,
                    perPage: perPage,
                    totalRecords: faqs.count,
                    totalPages: totalPages
                } as unknown as FaqPaginatedList;
            }
            throw new AppError(400, 'FAQ_REVISION_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // Restore revision
    restoreRevision = async (data: FaqRestoreRevisionServiceInput): Promise<faqObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const restoreFaqRevisionInput: FaqRestoreRevisionDaoInput = { id: data.id };
            let restoredEntiryId: number = await this.faqDao.restoreRevision(restoreFaqRevisionInput, { transaction });
            if (restoredEntiryId) {
                const getFaqByIdInput: FaqGetByIdDaoInput = { id: restoredEntiryId };
                let faq: faqObjectInteface = await this.faqDao.getFaqById(getFaqByIdInput, { transaction });
                await transaction.commit();
                return faq;
            }
            throw new AppError(400, 'FAQ_RESTORE_REVISION_ERROR_OUT', {});
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // set sort order
    setSortOrder = async (data: FaqSetSortOrderServiceInput): Promise<boolean> => {
        const transaction = await sequelize.transaction();
        try {
            const setSortOrderInput: FaqSetSortOrderDaoInput = { id: data.id, before: data.before, after: data.after };
            await this.faqDao.setSortOrder(setSortOrderInput, { transaction });
            await transaction.commit();
            return true;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // update email Template status
    updateStatus = async (data: FaqUpdateStatusServiceInput): Promise<faqObjectInteface> => {
        try {
            const getFaqByIdInput: FaqGetByIdDaoInput = { id: data.id };
            let faq = await this.faqDao.getFaqById(getFaqByIdInput);
            if (faq) {
                const updateFaqStatusInput: FaqUpdateStatusDaoInput = { id: data.id, status: data.status };
                await this.faqDao.updateStatus(updateFaqStatusInput);
                let faqObject = await this.getById(getFaqByIdInput);
                return faqObject;
            } else {
                throw new AppError(404, 'EMAIL_TEMPLATE_NOT_FOUND', { id: 'EMAIL_TEMPLATE_NOT_FOUND' });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    //get category Id
    getCategoryId = async (data: FaqGetCategoryIdServiceInput): Promise<number | null> => {
        try {
            const getCategoryIdInput: CategoryGetCategoryIdServiceInput = { code: data.categoryCode };
            let categoryId = await this.categoryService.getCategoryId(getCategoryIdInput);
            return categoryId;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }
}
