import { PostDao } from "../dao/post.dao";
import { AttachmentService } from "../services/attachment.service";
import { LanguageService } from "../services/language.service";
import { CategoryService } from "../services/category.service";
import { Common } from "../../utils/common";
import { AppError } from "../../utils/errors";
import { POST } from "../config/constants";
import { sequelize } from "../models";
import { CommentService } from "../services/comment.service";

export class PostService {
    private accountId: number | null;
    private userId: number | null;
    private language: string;
    private scope: string[] | null;
    private config: userConfig | null;
    private postDao: PostDao;
    private languageService: LanguageService;
    private categoryService: CategoryService;
    private attachmentService: AttachmentService;
    private commentService: CommentService;
    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.postDao = new PostDao({
            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
        });
        this.attachmentService = new AttachmentService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
        this.commentService = new CommentService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
    }

    private getCommentLimit = (): number => {
        const limit = Number(process.env.POST_COMMENT_LIMIT ?? 5);
        return Number.isFinite(limit) && limit > 0 ? limit : 5;
    }

    private getPostComments = async (postId: number): Promise<CommentPaginatedList> => {
        const perPage = this.getCommentLimit();
        const listRequest: CommentListRequestObject = {
            page: 1,
            perPage,
            sortBy: 'createdAt',
            sortDirection: 'DESC',
            postId,
            parentId: null
        };
        try {
            return await this.commentService.getList(listRequest);
        } catch (err) {
            if (err instanceof AppError && (err.statusCode === 401 || err.statusCode === 403)) {
                return { page: 1, perPage, totalRecords: 0, totalPages: 0, data: [] };
            }
            throw err;
        }
    }

    private attachLimitedContent = (post: PostObjectInteface): PostObjectInteface => {
        if (!post.limitedContent) {
            let content = (post.description || post.exerpt || post.descriptionText || null) as string | null;
            const limitedContent = content ? Common.limitHtmlWords(content, 500) : null;
            (post as PostObjectInteface & { limitedContent?: Text | null }).limitedContent = limitedContent as unknown as Text;
        }
        return post;
    }

    private applyPublicAccessRules = (post: PostObjectInteface): PostObjectInteface => {
        const isGuest = !this.userId;
        const isPremium = this.config?.isPremium || false;
        const isAdmin = (this.scope || []).includes('admin') || (this.scope || []).includes('managePost');

        this.attachLimitedContent(post);

        if (isAdmin) return post;

        const requiresLogin = post.visibility === POST.VISIBILITY.LOGGED_IN;
        const requiresSub = !!post.isExclusive;

        if ((requiresLogin && isGuest) || (requiresSub && !isPremium)) {
            post.description = null;
            post.descriptionText = null;
        }

        return post;
    }

    // accepts array of ids and string and return all ids
    generateTagIds = async (serviceInput: PostGenerateTagIdsServiceInput): Promise<number[]> => {
        const { tags } = serviceInput;
        const tagIds = [];
        for (const item of tags) {
            if (typeof item === "number") {
                tagIds.push(item);
            } else if (typeof item === "string") {
                const createCategoryInput: CategoryCreateServiceInput = {
                    categoryTypeCode: "tag",
                    imageId: null,
                    parentId: null,
                    status: null,
                    refLink: null,
                    name: item,
                    description: item as unknown as Text
                };
                try {
                    const category = await this.categoryService.createCategory(createCategoryInput);
                    if (category.id) {
                        tagIds.push(category.id);
                    } else {
                        throw new AppError(400, 'ERROR_CREATING_TAG', { value: item });
                    }
                } catch (err: any) {
                    if (err instanceof AppError && err.statusCode === 400 && err.message === 'CATEGORY_ALREADY_EXISTS') {
                        const code = `tag-${Common.slugify(item as string)}`;
                        const existingCategory = await this.categoryService.getByCode({ code });
                        if (existingCategory && existingCategory.id) {
                            tagIds.push(existingCategory.id);
                        } else {
                            throw err;
                        }
                    } else {
                        throw err;
                    }
                }
            }
        }
        return tagIds;
    }

    // create a new post content
    createPost = async (serviceInput: PostCreateServiceInput): Promise<PostObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const daoOptions: DaoOptions = { transaction };
            let {
                authorId,
                title,
                description,
                categoryIds,
                postType,
                status,
                visibility,
                isExclusive,
                publishedAt,
                isFeatured,
                exerpt,
                limitedContent,
                postAttachmentIds,
                featuredImageId,
                tags,
                metaTitle,
                metaDescription,
                metaKeywords,
                canonicalUrl,
                code: inputCode
            } = serviceInput;
            let postObj: PostObject = { postType, status, visibility, isExclusive, publishedAt, isFeatured, authorId };
            postObj.status = postObj.status ?? POST.STATUS.PUBLISH;
            if (postObj.status === POST.STATUS.PUBLISH && !postObj.publishedAt) {
                postObj.publishedAt = new Date();
            }

            if (typeof tags === 'string') {
                tags = (tags as string).split(',').map(t => t.trim()).filter(Boolean);
            }
            let finalCategoryIds: number[] = [];
            if (categoryIds && categoryIds.length > 0) {
                for (const cId of categoryIds) {
                    const verified = await this.categoryService.verifyCategoryId({ categoryId: cId, categoryTypeCode: "post" });
                    if (!verified) throw new AppError(400, "INVALID_POST_CATEGORY", { categoryId: cId });
                    finalCategoryIds.push(cId);
                }
            }

            const genMetaTitle = metaTitle || title || "";
            let textDesc = exerpt ? exerpt.toString() : "";
            if (!textDesc && description) { textDesc = await Common.convertHtmlToText(description.toString()) as string; }
            const genMetaDesc = metaDescription || textDesc.substring(0, 160) || "";
            const genMetaKeywords = metaKeywords || (tags ? tags.join(", ") : title) || "";

            let postContentObj: PostContentObject = { title, description, exerpt, limitedContent, metaTitle: genMetaTitle, metaDescription: genMetaDesc, metaKeywords: genMetaKeywords, canonicalUrl };
            let tagIds: number[] | null = null;

            const languages: LanguageInfo = await this.languageService.getRequestedAndDefaultLanguage();
            const code = (inputCode || "").trim() || Common.slugify(postContentObj.title);
            postObj.code = code;
            postContentObj.descriptionText = (postContentObj.description ? await Common.convertHtmlToText(postContentObj.description.toString()) : null) as unknown as Text;

            const doExistsByCodeDaoInput: PostDoExistsByCodeDaoInput = { code, postType: postObj.postType };
            const exists = await this.postDao.doExistsByCode(doExistsByCodeDaoInput, daoOptions);
            if (exists) {
                throw new AppError(400, 'POST_CONTENT_ALREADY_EXISTS', { name: 'POST_CONTENT_ALREADY_EXISTS' });
            }

            if (postAttachmentIds) {
                const filesExists = await this.attachmentService.verifyFile(postAttachmentIds);
                if (filesExists.length != postAttachmentIds.length) {
                    throw new AppError(404, 'INVALID_ATTACHMENTS_FOUND', { postAttachmentIds: 'INVALID_ATTACHMENTS_FOUND' });
                }
            }

            if (tags) {
                const generateTagIdsServiceInput: PostGenerateTagIdsServiceInput = { tags };
                tagIds = await this.generateTagIds(generateTagIdsServiceInput);
            }

            const createPostDaoInput: PostCreateDaoInput = { postObj, postContentObj, postAttachments: postAttachmentIds, featuredImageId, languages, postTagsIds: tagIds, postCategoriesIds: finalCategoryIds.length > 0 ? finalCategoryIds : null };
            let postIdentifier: number = await this.postDao.create(createPostDaoInput, daoOptions);
            const setSortOrderDaoInput: PostSetSortOrderDaoInput = { id: postIdentifier, postType: postObj.postType };
            await this.postDao.setSortOrder(setSortOrderDaoInput, daoOptions);
            await transaction.commit();

            const getPostByIdDaoInput: PostGetByIdDaoInput = { id: postIdentifier, postType: postObj.postType, expanded: true };
            let post = await this.postDao.getPostById(getPostByIdDaoInput, {});
            return post;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // update the existing post content
    updatePost = async (serviceInput: PostUpdateServiceInput): Promise<PostObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const daoOptions: DaoOptions = { transaction };
            let {
                id,
                authorId,
                title,
                description,
                categoryIds,
                postType,
                isFeatured,
                status,
                visibility,
                isExclusive,
                publishedAt,
                exerpt,
                limitedContent,
                postAttachmentIds,
                featuredImageId,
                tags,
                metaTitle,
                metaDescription,
                metaKeywords,
                canonicalUrl
            } = serviceInput;
            let postObj: PostObject = { postType, isFeatured, authorId };


            let finalCategoryIds: number[] = [];
            if (categoryIds && categoryIds.length > 0) {
                for (const cId of categoryIds) {
                    const verified = await this.categoryService.verifyCategoryId({ categoryId: cId, categoryTypeCode: "post" });
                    if (!verified) throw new AppError(400, "INVALID_POST_CATEGORY", { categoryId: cId });
                    finalCategoryIds.push(cId);
                }
            }
            if (status !== undefined) postObj.status = status;
            if (visibility !== undefined) postObj.visibility = visibility;
            if (isExclusive !== undefined) postObj.isExclusive = isExclusive;
            if (publishedAt !== undefined) {
                postObj.publishedAt = publishedAt;
            } else if (postObj.status === POST.STATUS.PUBLISH) {
                const existingPost = await this.postDao.getPostById({ id, postType: postObj.postType }, daoOptions);
                if (!existingPost.publishedAt) {
                    postObj.publishedAt = new Date();
                }
            }

            if (typeof tags === 'string') {
                tags = (tags as string).split(',').map(t => t.trim()).filter(Boolean);
            }

            const genMetaTitle = metaTitle || title || "";
            let textDesc = exerpt ? exerpt.toString() : "";
            if (!textDesc && description) { textDesc = await Common.convertHtmlToText(description.toString()) as string; }
            const genMetaDesc = metaDescription || textDesc.substring(0, 160) || "";
            const genMetaKeywords = metaKeywords || (tags ? tags.join(", ") : title) || "";

            let postContentObj: PostContentObject = { title, description, exerpt, limitedContent, metaTitle: genMetaTitle, metaDescription: genMetaDesc, metaKeywords: genMetaKeywords, canonicalUrl };
            let tagIds: number[] | null = null;

            const languages: LanguageInfo = await this.languageService.getRequestedAndDefaultLanguage();
            const code = Common.slugify(postContentObj.title);
            postContentObj.descriptionText = (postContentObj.description ? await Common.convertHtmlToText(postContentObj.description.toString()) : null) as unknown as Text;

            const doExistsByIdDaoInput: PostDoExistsByIdDaoInput = { id, postType: postObj.postType };
            const exists = await this.postDao.doExistsById(doExistsByIdDaoInput, daoOptions);
            if (!exists) {
                throw new AppError(404, 'POST_CONTENT_NOT_FOUND', { title: 'POST_CONTENT_NOT_FOUND' });
            }

            if (code) {
                const doExistsByCodeDaoInput: PostDoExistsByCodeDaoInput = { code, postType: postObj.postType, excludeId: id };
                let codeInUse = await this.postDao.doExistsByCode(doExistsByCodeDaoInput, daoOptions);
                if (codeInUse) {
                    throw new AppError(400, 'POST_CONTENT_ALREADY_EXISTS', { title: 'POST_CONTENT_ALREADY_EXISTS' });
                }
            }
            if (postAttachmentIds) {
                const filesExists = await this.attachmentService.verifyFile(postAttachmentIds);
                if (filesExists.length != postAttachmentIds.length) {
                    throw new AppError(404, 'INVALID_ATTACHMENTS_FOUND', { postAttachmentIds: 'INVALID_ATTACHMENTS_FOUND' });
                }
            }
            if (tags) {
                const generateTagIdsServiceInput: PostGenerateTagIdsServiceInput = { tags };
                tagIds = await this.generateTagIds(generateTagIdsServiceInput);
            }

            const updatePostDaoInput: PostUpdateDaoInput = { id, postObj, postContentObj, postAttachments: postAttachmentIds, featuredImageId, languages, postTagsIds: tagIds, postCategoriesIds: finalCategoryIds.length > 0 ? finalCategoryIds : null };
            await this.postDao.update(updatePostDaoInput, daoOptions);
            await transaction.commit();

            const getPostByIdDaoInput: PostGetByIdDaoInput = { id, postType: postObj.postType };
            let post = await this.postDao.getPostById(getPostByIdDaoInput, {});
            return post;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // delete the existing post content
    deletePost = async (serviceInput: PostDeleteServiceInput): Promise<PostObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const daoOptions: DaoOptions = { transaction };
            const { id, postType } = serviceInput;
            const doExistsByIdDaoInput: PostDoExistsByIdDaoInput = { id, postType };
            const exists = await this.postDao.doExistsById(doExistsByIdDaoInput, daoOptions);
            if (!exists) {
                throw new AppError(404, 'POST_CONTENT_NOT_FOUND', { title: 'POST_CONTENT_NOT_FOUND' });
            }
            const deletePostDaoInput: PostDeleteDaoInput = { id, postType };
            await this.postDao.delete(deletePostDaoInput, daoOptions);
            await transaction.commit();

            const getPostByIdDaoInput: PostGetByIdDaoInput = { id, postType, expanded: true, paranoid: false };
            let post = await this.postDao.getPostById(getPostByIdDaoInput, {});
            if (post) {
                // Access Control Logic
                const isGuest = !this.userId;
                const isPremium = this.config?.isPremium || false;

                const requiresLogin = post.visibility === POST.VISIBILITY.LOGGED_IN;
                const requiresSub = post.isExclusive;

                if ((requiresLogin && isGuest) || (requiresSub && !isPremium)) {
                    post.description = null;
                    post.descriptionText = null;
                    // Provide excerpt only
                }
            }
            return post;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get post content by id
    getById = async (serviceInput: PostGetByIdServiceInput): Promise<PostObjectInteface> => {
        try {
            const { id, postType, fullInfo = true } = serviceInput;
            const getPostByIdDaoInput: PostGetByIdDaoInput = { id, postType, expanded: fullInfo };
            let post = await this.postDao.getPostById(getPostByIdDaoInput, {});
            if (post) {
                return post;
            }
            throw new AppError(404, 'POST_CONTENT_NOT_FOUND', { title: 'POST_CONTENT_NOT_FOUND' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get post content by id
    publicGetById = async (serviceInput: PostPublicGetByIdServiceInput): Promise<PostObjectInteface> => {
        try {
            const { id, postType = '', fullInfo = true } = serviceInput;
            const getPostByIdDaoInput: PostGetByIdDaoInput = { id, postType, expanded: fullInfo, paranoid: true, trackIncludes: true };
            let post = await this.postDao.getPostById(getPostByIdDaoInput, {});
            if (post) {
                return this.applyPublicAccessRules(post);
            }
            throw new AppError(404, 'POST_CONTENT_NOT_FOUND', { title: 'POST_CONTENT_NOT_FOUND' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get post content by code
    getByCode = async (serviceInput: PostGetByCodeServiceInput): Promise<PostObjectInteface> => {
        try {
            const { code, postType, fullInfo = true } = serviceInput;
            const getPostByCodeDaoInput: PostGetByCodeDaoInput = { code, postType, expanded: fullInfo };
            let post = await this.postDao.getPostByCode(getPostByCodeDaoInput, {});
            if (post) {
                return post;
            }
            throw new AppError(404, 'POST_CONTENT_NOT_FOUND', { title: 'POST_CONTENT_NOT_FOUND' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list post content
    getPost = async (serviceInput: PostGetPostsServiceInput): Promise<PostPaginatedList> => {
        try {
            const { postType, listRequest } = serviceInput;
            const { page, perPage } = listRequest;
            const getPostListDaoInput: PostGetPostListDaoInput = { postType, listRequest };
            let post: PostPaginatedData = await this.postDao.getPostList(getPostListDaoInput, {});
            if (post) {
                let totalPages = Common.getTotalPages(post.count, perPage);
                const rows = (post.rows as unknown as PostObjectInteface[]);
                return {
                    data: rows,
                    page: page,
                    perPage: perPage,
                    totalRecords: post.count,
                    totalPages: totalPages
                } as unknown as PostPaginatedList;
            }
            throw new AppError(400, 'POST_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list public post content
    getPostByTags = async (serviceInput: PostGetPostsByTagsServiceInput): Promise<PostPaginatedList> => {
        try {
            const { listRequest } = serviceInput;
            const { page, perPage } = listRequest;
            const getPostByTagsDaoInput: PostGetPostByTagsDaoInput = { listRequest };
            let post: PostPaginatedData = await this.postDao.getPostByTags(getPostByTagsDaoInput, {});
            if (post) {
                let totalPages = Common.getTotalPages(post.count, perPage);
                const rows = (post.rows as unknown as PostObjectInteface[]);
                return {
                    data: rows,
                    page: page,
                    perPage: perPage,
                    totalRecords: post.count,
                    totalPages: totalPages
                } as unknown as PostPaginatedList;
            }
            throw new AppError(400, 'POST_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list all post content
    getAllPost = async (serviceInput: PostGetAllPostsServiceInput): Promise<PostObjectSummaryInteface[]> => {
        try {
            const { postType, listRequest } = serviceInput;
            const getAllPostDaoInput: PostGetAllPostDaoInput = { postType, listRequest };
            let post = await this.postDao.getAllPost(getAllPostDaoInput, {});
            if (post) {
                return (post as unknown as PostObjectInteface[]) as unknown as PostObjectSummaryInteface[];
            }
            throw new AppError(400, 'POST_CONTENT_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list post content revisions
    getPostRevisions = async (serviceInput: PostGetPostRevisionsServiceInput): Promise<PostPaginatedList> => {
        try {
            const { id, postType, listRequest } = serviceInput;
            const { page, perPage } = listRequest;
            const getPostRevisionListDaoInput: PostGetPostRevisionListDaoInput = { id, postType, listRequest };
            let post = await this.postDao.getPostRevisionList(getPostRevisionListDaoInput, {});
            if (post) {
                let totalPages = Common.getTotalPages(post.count, perPage);
                const rows = (post.rows as unknown as PostObjectInteface[]);
                return {
                    data: rows,
                    page: page,
                    perPage: perPage,
                    totalRecords: post.count,
                    totalPages: totalPages
                } as unknown as PostPaginatedList;
            }
            throw new AppError(400, 'POST_REVISION_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // Restore post content revision
    restoreRevision = async (serviceInput: PostRestoreRevisionServiceInput): Promise<PostObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const daoOptions: DaoOptions = { transaction };
            const { id, postType } = serviceInput;
            const restoreRevisionDaoInput: PostRestoreRevisionDaoInput = { id, postType };
            let restoredEntiryId: number = await this.postDao.restoreRevision(restoreRevisionDaoInput, daoOptions);
            await transaction.commit();
            if (restoredEntiryId) {
                const getPostByIdDaoInput: PostGetByIdDaoInput = { id: restoredEntiryId, postType };
                let post: PostObjectInteface = await this.postDao.getPostById(getPostByIdDaoInput, {});
                return post;
            }
            throw new AppError(400, 'CATEGORY_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 post content sort order
    setSortOrder = async (serviceInput: PostSetSortOrderServiceInput): Promise<boolean> => {
        const transaction = await sequelize.transaction();
        try {
            const daoOptions: DaoOptions = { transaction };
            const { id, postType, before, after } = serviceInput;
            const setSortOrderDaoInput: PostSetSortOrderDaoInput = { id, postType, before, after };
            let sortOrder: boolean = await this.postDao.setSortOrder(setSortOrderDaoInput, daoOptions);
            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 post content status
    updateStatus = async (serviceInput: PostUpdateStatusServiceInput): Promise<PostObjectInteface> => {
        try {
            const { id, postType, status } = serviceInput;
            const getPostByIdDaoInput: PostGetByIdDaoInput = { id, postType };
            let category = await this.postDao.getPostById(getPostByIdDaoInput, {});
            if (category) {
                const updateStatusDaoInput: PostUpdateStatusDaoInput = { id, postType, status };
                await this.postDao.updateStatus(updateStatusDaoInput, {});
                const getByIdServiceInput: PostGetByIdServiceInput = { id, postType, fullInfo: true };
                let post = await this.getById(getByIdServiceInput);
                return post;
            } else {
                throw new AppError(404, 'POST_CONTENT_NOT_FOUND', { id: 'POST_CONTENT_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 (serviceInput: PostGetCategoryIdServiceInput): Promise<number | null> => {
        try {
            const { categoryCode } = serviceInput;
            const getCategoryIdInput: CategoryGetCategoryIdServiceInput = { code: 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);
        }
    }

    getPublicPostByCode = async (serviceInput: PostGetPublicPostByCodeServiceInput): Promise<PostObjectInteface> => {
        try {
            const { code, fullInfo = true } = serviceInput;
            const getPublicPostByCodeDaoInput: PostGetPublicPostByCodeDaoInput = { code, expanded: fullInfo, paranoid: true };
            let post = await this.postDao.getPublicPostByCode(getPublicPostByCodeDaoInput, {});
            if (post) {
                const gated = this.applyPublicAccessRules(post);
                gated.comments = await this.getPostComments(gated.id);
                return gated;
            }
            throw new AppError(404, 'POST_CONTENT_NOT_FOUND', { title: 'POST_CONTENT_NOT_FOUND' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    getPublicPageByCode = async (serviceInput: PostGetPublicPostByCodeServiceInput): Promise<PostObjectInteface> => {
        try {
            const { code, fullInfo = true } = serviceInput;
            const getPublicPostByCodeDaoInput: PostGetPublicPostByCodeDaoInput = { code, expanded: fullInfo, paranoid: true };
            let post = await this.postDao.getPublicPostByCode(getPublicPostByCodeDaoInput, {});
            if (post) {
                return post;
            }
            throw new AppError(404, 'POST_CONTENT_NOT_FOUND', { title: 'POST_CONTENT_NOT_FOUND' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    getPublicPageList = async (serviceInput: PostGetPublicPostListServiceInput): Promise<PostPaginatedList> => {
        try {
            const { listRequest } = serviceInput;
            const { page, perPage } = listRequest;
            const getPublicPostListDaoInput: PostGetPublicPostListDaoInput = { listRequest };
            let postData = await this.postDao.getPublicPageList(getPublicPostListDaoInput, {});
            const rows = (postData.rows as unknown as PostObjectInteface[]).map((post) => this.applyPublicAccessRules(post));
            return {
                data: rows,
                page,
                perPage,
                totalRecords: postData.count,
                totalPages: Common.getTotalPages(postData.count, perPage)
            };
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list published posts publicly (no auth needed)
    getPublicPostList = async (serviceInput: PostGetPublicPostListServiceInput): Promise<PostPaginatedList> => {
        try {
            const { listRequest } = serviceInput;
            const { page, perPage } = listRequest;
            const daoInput: PostGetPublicPostListDaoInput = { listRequest };
            let result = await this.postDao.getPublicPostList(daoInput, {});
            if (result) {
                const rows = (result.rows as unknown as PostObjectInteface[]).map((post) => this.applyPublicAccessRules(post));
                let totalPages = Common.getTotalPages(result.count, perPage);
                return {
                    data: rows,
                    page,
                    perPage,
                    totalRecords: result.count,
                    totalPages
                } as unknown as PostPaginatedList;
            }
            throw new AppError(400, 'POST_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }
}
