import { Sequelize, WhereOptions, literal, Op } from "sequelize";
import Models from "../models";
import { LocalizedContent } from "../../utils/contentGenerator";
import { AppError } from "../../utils/errors";
import { Common } from "../../utils/common";
import _ from 'lodash';

const testimonialAttributes: AttributeElement[] = [
    'id', 'status', 'name', 'designation', 'createdAt', 'updatedAt',
    [literal('(case when `localizedContent`.content is not null then `localizedContent`.content else `defaultContent`.content END)'), 'content'],
    [
        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 attachments a
          WHERE a.id = \`Testimonial\`.\`image_id\`
          LIMIT 1
        )`),
        "image"
    ]
];

export class TestimonialDao {
    private accountId: number | null;
    private userId: number | null;
    private language: string;
    
    constructor(
        private options: {
            language: string;
            accountId: number | null;
            userId: number | null;
        } = {
                language: process.env.DEFAULT_LANGUAGE_CODE!,
                userId: null,
                accountId: null,
            }
    ) {
        this.language = options.language;
        this.userId = options.userId ?? null
        this.accountId = options.accountId ?? null
    }

    // get association for the entity
    private includeAssociations = (fullInfo: boolean = false): IncludeOption[] => {
        const includeModels: IncludeOption[] = [
            {
                attributes: [],
                model: Models.TestimonialContent,
                as: 'localizedContent',
                include: [{ attributes: [], model: Models.Language, as: 'language', where: { code: this.language } }]
            },
            {
                attributes: [],
                model: Models.TestimonialContent,
                as: 'defaultContent',
                include: [{ attributes: [], model: Models.Language, as: 'language', where: { code: process.env.DEFAULT_LANGUAGE_CODE } }]
            }
        ];
        return includeModels;
    }

    // get testimonial
    getTestimonial = async (data: TestimonialGetDaoInput, options: DaoOptions = {}): Promise<testimonialObjectInteface> => {
        try {
            const { id = null, paranoid = false } = data;
            const testimonial = await Models.Testimonial.findOne({
                attributes: testimonialAttributes,
                where: { id: id, accountId: this.accountId },
                include: this.includeAssociations(true),
                paranoid: paranoid,
                transaction: options.transaction
            });
            return JSON.parse(JSON.stringify(testimonial)) as unknown as testimonialObjectInteface;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // get testimonial by id
    getTestimonialById = async (data: TestimonialGetByIdDaoInput, options: DaoOptions = {}): Promise<testimonialObjectInteface> => {
        try {
            const getTestimonialInput: TestimonialGetDaoInput = { id: data.id, paranoid: data.paranoid ?? true };
            return await this.getTestimonial(getTestimonialInput, options);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // check if testimonial exists by id
    doExistsById = async (data: TestimonialIdentifierObject, options: DaoOptions = {}): Promise<TestimonialInterface | false> => {
        try {
            const { id } = data;
            const testimonial = await Models.Testimonial.findOne({
                attributes: ['id'],
                where: { id: id, accountId: this.accountId },
                transaction: options.transaction
            });
            return testimonial?.id ? testimonial : false;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // create a new testimonial
    create = async (data: TestimonialCreateDaoInput, options: DaoOptions = {}): Promise<number> => {
        try {
            const { testimonialObj, testimonialContentObj, languages } = data;
            const testimonialContents: testimonialContentObject[] = LocalizedContent.generate(testimonialContentObj, languages) as unknown as testimonialContentObject[];
            const testimonialObject: testimonialDataObject = { ...testimonialObj, userId: this.userId, accountId: this.accountId, testimonialContents: testimonialContents };
            let testimonial = await Models.Testimonial.create(testimonialObject, {
                include: [{ model: Models.TestimonialContent, as: 'testimonialContents' }],
                transaction: options.transaction
            });
            return testimonial.id;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // update testimonial
    update = async (data: TestimonialUpdateDaoInput, options: DaoOptions = {}): Promise<void> => {
        try {
            const { id, testimonialObj, testimonialContentObj, languages } = data;
            await Models.Testimonial.update({ ...testimonialObj, lastUpdatedBy: this.userId }, { where: { id: id }, transaction: options.transaction });
            
            let verification = await Models.TestimonialContent.findOne({ where: { testimonialId: id, languageId: languages.requested.id }, transaction: options.transaction });
            if (verification) {
                let testimonialContent: testimonialContentObject = { ...testimonialContentObj, ...{ languageId: languages.requested.id, testimonialId: id } }
                await Models.TestimonialContent.update(testimonialContent, { where: { id: verification.id }, transaction: options.transaction });
            } else {
                let testimonialContent: testimonialContentObject = { ...testimonialContentObj, ...{ languageId: languages.requested.id, testimonialId: id } }
                await Models.TestimonialContent.create(testimonialContent, { transaction: options.transaction });
            }
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // delete testimonial
    delete = async (data: TestimonialDeleteDaoInput, options: DaoOptions = {}): Promise<void> => {
        try {
            const { id } = data;
            await Models.Testimonial.destroy({ where: { id: id, accountId: this.accountId }, transaction: options.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 [['name', direction]];
            case 'id':
                return [['id', direction]];
            default:
                return [['id', 'ASC']];
        }
    }

    // Build filter
    private buildFilter = (where: WhereOptions & { [Op.and]: any[] }, searchText: string | null, status: number | null) => {
        let alphaString = '';
        if (searchText) {
            let searchData: searchText = Common.prepareSearchText(searchText);
            if (searchData.alphaString) {
                alphaString = '*' + searchData.alphaString + '*'
                ;(where[Op.and] as any[]).push({
                    [Op.or]: [
                        { name: { [Op.like]: `%${searchData.alphaString}%` } },
                        { designation: { [Op.like]: `%${searchData.alphaString}%` } },
                        Sequelize.literal('MATCH(`defaultContent`.`content`) AGAINST(:alphaString IN BOOLEAN MODE)')
                    ]
                });
            }
        }
        if (status != null) {
            where = { ...where, status: status }
        }
        return { where: where, replacements: { alphaString: alphaString } }
    }

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

    // list all testimonials
    getAllTestimonials = async (data: TestimonialGetAllTestimonialsDaoInput, options: DaoOptions = {}): Promise<testimonialObjectSummaryInteface[]> => {
        try {
            const { listRequest } = data;
            const { searchText, sortBy, sortDirection, status } = listRequest
            let where: WhereOptions & { [Op.and]: any[] } = { accountId: this.accountId, [Op.and]: [] };
            let applyfilter = this.buildFilter(where, searchText, status);
            const orderBy = this.buildOrderBy(sortBy, sortDirection);
            const testimonials = await Models.Testimonial.findAll({
                attributes: testimonialAttributes,
                where: applyfilter.where,
                replacements: applyfilter.replacements,
                include: this.includeAssociations(false),
                order: orderBy,
                limit: +process.env.LIST_ALL_LIMIT!,
                transaction: options.transaction
            });
            return JSON.parse(JSON.stringify(testimonials)) as unknown as testimonialObjectSummaryInteface[];
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

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