import { Sequelize, WhereOptions, FindOptions, literal, fn, col, Op } from "sequelize";
import Models from "../models";
import { AppError } from "../../utils/errors";
import { Common } from "../../utils/common"

const contactUsAttributes: AttributeElement[] = ['id', 'createdAt', 'updatedAt', 'name', 'email', 'subject', 'message'];

const authorAttributes: AttributeElement[] = [
    'id', 'onlineStatus',
    [literal('`author->userProfile`.`name`'), 'name'],
    [
        literal(`(
              SELECT JSON_OBJECT(
                'id', a.id,
                'fileName', a.file_name,
                'uniqueName', a.unique_name,
                'filePath', CONCAT('${process.env.PROTOCOL}://', '${process.env.API_HOST}', '/attachment/', a.unique_name),
                'cdnUrl', CONCAT('${process.env.CDN_PATH}', '/attachment/', a.unique_name)
              )
              FROM user_profile as up
              JOIN attachments as a ON up.profile_image_id = a.id
              WHERE up.user_id = author.id
              LIMIT 1
            )`),
        'profileImage'
    ]
];

const updatedByAttributes: AttributeElement[] = [
    'id', 'onlineStatus',
    [literal('`updatedBy->userProfile`.`name`'), 'name'],
    [
        literal(`(
              SELECT JSON_OBJECT(
                'id', a.id,
                'fileName', a.file_name,
                'uniqueName', a.unique_name,
                'filePath', CONCAT('${process.env.PROTOCOL}://', '${process.env.API_HOST}', '/attachment/', a.unique_name),
                'cdnUrl', CONCAT('${process.env.CDN_PATH}', '/attachment/', a.unique_name)
              )
              FROM user_profile as up
              JOIN attachments as a ON up.profile_image_id = a.id
              WHERE up.user_id = author.id
              LIMIT 1
            )`),
        'profileImage'
    ]
];

const attachmentAttributes: AttributeElement[] = [
    'id',
    'fileName',
    'uniqueName',
    [fn('CONCAT', process.env.PROTOCOL, '://', process.env.API_HOST, "/attachment/", literal('`attachments`.`unique_name`')), 'filePath'],
    [fn('CONCAT', process.env.CDN_PATH, literal('`attachments`.`file_path`')), 'cdnUrl'],
]

export class ContactUsDao {
    private accountId: number | null;
    private userId: number | null;
    private language: string;
    private scope: string[] | null;
    private config: userConfig | null;
    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
    }

    // Get full faq object
    private getFullObject = async (data: ContactUsGetFullObjectDaoInput, options: DaoOptions = {}): Promise<ContactUsInterface> => {
        const { id } = data;
        const { transaction } = options;
        try {
            let contactus = await Models.ContactUs.findOne({
                where: { id: id },
                transaction
            });
            return JSON.parse(JSON.stringify(contactus))
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'ERROR_WHILE_GETTING_FULL_DATA', err);
        }
    }

    // get association for the entity
    private includeAssociations = (fullInfo: boolean = false): IncludeOption[] => {
        const includeModels: IncludeOption[] = [];
        if (fullInfo) {
            includeModels.push(
                {
                    attributes: authorAttributes,
                    model: Models.User,
                    as: 'author',
                    include: [{ attributes: [], model: Models.UserProfile, as: 'userProfile', include: [{ model: Models.Attachment, as: "profileImage" }] }]
                },
                {
                    attributes: updatedByAttributes,
                    model: Models.User,
                    as: 'updatedBy',
                    include: [{ attributes: [], model: Models.UserProfile, as: 'userProfile', include: [{ model: Models.Attachment, as: "profileImage" }] }]
                },
                {
                    attributes: attachmentAttributes,
                    model: Models.Attachment,
                    as: 'attachments',
                    through: { attributes: [] }
                }
            );
        }
        return includeModels;
    }

    // get faq
    getContactUs = async (data: ContactUsGetDaoInput, options: DaoOptions = {}): Promise<ContactUsInterface> => {
        const { id } = data;
        const { transaction } = options;
        try {
            const contactUs = await Models.ContactUs.findOne({
                attributes: contactUsAttributes,
                where: { id },
                include: this.includeAssociations(true),
                transaction
            });
            return JSON.parse(JSON.stringify(contactUs)) as unknown as ContactUsInterface;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // get faq by id
    getConatctUsById = async (data: ContactUsGetByIdDaoInput, options: DaoOptions = {}): Promise<ContactUsInterface> => {
        try {
            const getContactUsInput: ContactUsGetDaoInput = { id: data.id };
            return await this.getContactUs(getContactUsInput, options);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // create a new faq
    create = async (data: ContactUsCreateDaoInput, options: DaoOptions = {}): Promise<number> => {
        const { name, email, subject, message, attachments } = data;
        const { transaction } = options;
        try {
            const contactus = await Models.ContactUs.create(
                {name, email, subject, message, userId: this.userId, accountId: this.accountId}, 
                { transaction}
            );
            if(attachments && attachments.length > 0) {
                const count = await Models.Attachment.count({ where: { id: attachments } });
                if(count === attachments.length) {
                    await contactus.addAttachments(attachments, { transaction });
                }
            }
            return contactus.id;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // delete faq
    delete = async (data: ContactUsDeleteDaoInput, options: DaoOptions = {}): Promise<void> => {
        const { id } = data;
        const { transaction } = options;
        try {
            // let contactus = await this.getContactUsById(id, false);
            await Models.ContactUs.destroy({ where: { id: id }, transaction: transaction });
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // Build order by clause
    private buildOrderBy = (field: string, direction: string = 'desc') => {
        switch (field) {
            case 'sort-order':
                return [['sortOrder', direction]];
            case 'name':
                return [[literal(`name`), direction]];
            case 'id':
                return [['id', direction]];
            default:
                // Fallback to default order
                return [
                    ['id', 'ASC']
                ];
        }
    }

    // Build filter
    private buildFilter = (where: WhereOptions & { [Op.and]: any[] }, searchText: string | null) => {
        let alphaString = '';
        let specialString = '';
        if (searchText) {
            let searchData: searchText = Common.prepareSearchText(searchText);
            if (searchData.alphaString) {
                alphaString = '*' + searchData.alphaString + '*'
            } if (searchData.specialString) {
                specialString = searchData.specialString
            }
            if (alphaString) {
                (where[Op.and] as any[]).push(Sequelize.literal('MATCH(`message`) AGAINST(:alphaString IN BOOLEAN MODE)'));
            } if (specialString) {
                (where[Op.and] as any[]).push(Sequelize.literal('MATCH(`message`) AGAINST(:specialString IN BOOLEAN MODE)'));
            }
        }
        return { where: where, replacements: { alphaString: alphaString, specialString: specialString } }
    }

    // list faq with pagination
    list = async (data: ContactUsListDaoInput, options: DaoOptions = {}): Promise<ContactUsPaginatedData> => {
        const { listRequest } = data;
        const { transaction } = options;
        try {
            const { page, perPage, searchText, sortBy, sortDirection } = listRequest
            let offset = (page - 1) * perPage;
            let where: WhereOptions & { [Op.and]: any[] } = { [Op.and]: [] };
            let applyfilter = this.buildFilter(where, searchText);
            const orderBy = this.buildOrderBy(sortBy, sortDirection);
            const contactus = await Models.ContactUs.findAndCountAll({
                attributes: contactUsAttributes,
                where: applyfilter.where,
                include: this.includeAssociations(true),
                replacements: applyfilter.replacements,
                offset: offset,
                limit: perPage,
                order: orderBy,
                distinct: true,
                transaction
            });
            return JSON.parse(JSON.stringify(contactus)) as unknown as ContactUsPaginatedData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // update faq status
    updateStatus = async (data: ContactUsUpdateStatusDaoInput, options: DaoOptions = {}): Promise<void> => {
        const { id, status } = data;
        const { transaction } = options;
        try {
            await Models.ContactUs.update({ status: status }, { where: { id: id }, transaction });
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }
}
