import { CampaignDao } from "../dao/campaign.dao";
import { AttachmentService } from "../services/attachment.service";
import { UserService } from "../services/user.service";
import { LanguageService } from "../services/language.service";
import { sequelize } from "../models";
import { Common } from "../../utils/common";
import { AppError } from "../../utils/errors";
import { CAMPAIGN } from "../config/constants";

export class CampaignService {
    private accountId: number | null;
    private userId: number | null;
    private language: string;
    private scope: string[] | null;
    private config: userConfig | null;
    private campaignDao: CampaignDao;
    private languageService: LanguageService;
    private attachmentService: AttachmentService;
    private userService: UserService;
    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.campaignDao = new CampaignDao({
            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
        });
        this.userService = new UserService({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });

    }
    // create a new Campaign
    createCampaign = async (data: CampaignCreateServiceInput): Promise<CampaignObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { challengeId = null } = data;
            const campaignObj: CampaignRequestObject = {
                ...data,
                status: data.status ?? CAMPAIGN.STATUS.ACTIVE,
                isUserAssigned: true
            };
            if(new Date(campaignObj.sendAt!) < new Date()) {
                throw new AppError(400, 'SELECT_UPCOMPING_DATE', { sendAt: 'SELECT_UPCOMPING_DATE' });
            }
            
            const code = Common.slugify(campaignObj.title);
            campaignObj.code = code + new Date();
            campaignObj.descriptionText = (campaignObj.description ? await Common.convertHtmlToText(campaignObj.description.toString()) : null) as unknown as Text
            const doExistsByCodeInput: CampaignDoExistsByCodeDaoInput = { code };
            const exists = await this.campaignDao.doExistsByCode(doExistsByCodeInput);
            if (campaignObj.parentId) {
                // check if parent campaign exists and have same type
                const parentExistsInput: CampaignDoExistsByIdDaoInput = { id: campaignObj.parentId };
                const parentExists = await this.campaignDao.doExistsById(parentExistsInput);
                if (!parentExists) {
                    throw new AppError(404, 'PARENT_CAMPAIGN_NOT_FOUND', { parentId: 'PARENT_CAMPAIGN_NOT_FOUND' });
                }
            }
            if (campaignObj.attachmentId) {
                // check if parent campaign exists and have same type
                const imageExists = await this.attachmentService.verifyFile([campaignObj.attachmentId]);
                if (imageExists.length != 1) {
                    throw new AppError(404, 'ATTACHMENT_NOT_FOUND', { attachmentId: 'ATTACHMENT_NOT_FOUND' });
                }
            }
            // check if campaign exists


            if (exists) {
                throw new AppError(400, 'CAMPAIGN_ALREADY_EXISTS', { title: 'CAMPAIGN_ALREADY_EXISTS' });
            }
            const createCampaignInput: CampaignCreateDaoInput = { campaignObj: campaignObj as CampaignObject, challengeId };
            let campaignIdentifier: number = await this.campaignDao.create(createCampaignInput, { transaction });
            const setSortOrderInput: CampaignSetSortOrderDaoInput = { id: campaignIdentifier };
            await this.campaignDao.setSortOrder(setSortOrderInput, { transaction })
            const getCampaignByIdInput: CampaignGetByIdDaoInput = { id: campaignIdentifier, fullInfo: true };
            let campaign = await this.campaignDao.getCampaignById(getCampaignByIdInput, { transaction })
            await transaction.commit();
            return campaign;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // create a new Campaign
    cloneCampaign = async (): Promise<void> => {
        try {
            const leadCampaign = process.env.LEAD_CAMPAIGN_IDS!;
            const campaignIds = leadCampaign?.split(",");
            for(let id of campaignIds) {
                const getFullObjectInput: CampaignGetFullObjectDaoInput = { id: +id };
                const campaign = await this.campaignDao.getFullObject(getFullObjectInput);
                if(campaign) {
                    const clonedCampaign = await this.campaignDao.cloneCampaign(+id);
                    console.log(clonedCampaign, " ============ clonedCampaign")
                    const setSortOrderInput: CampaignSetSortOrderDaoInput = { id: clonedCampaign.id };
                    await this.campaignDao.setSortOrder(setSortOrderInput)
                }
            }
           return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // create a new Campaign
    resetCampaign = async (): Promise<void> => {
        try {
            const clonedCampaign = await this.campaignDao.resetCampaign();
           return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // update the existing campaign
    updateCampaign = async (data: CampaignUpdateServiceInput): Promise<CampaignObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { id } = data;
            const campaignObj: CampaignObject = { ...data, status: data.status ?? CAMPAIGN.STATUS.ACTIVE };
            if(new Date(campaignObj.sendAt!) < new Date()) {
                throw new AppError(400, 'PAST_CAMPAIGN_CANNOT_BE_UPDATED', {});
            }

            const code = Common.slugify(campaignObj.title);

            if (code) {
                const doExistsByCodeInput: CampaignDoExistsByCodeDaoInput = { code, excludeId: id };
                let codeInUse = await this.campaignDao.doExistsByCode(doExistsByCodeInput, { transaction });
                if (codeInUse) {
                    throw new AppError(400, 'CAMPAIGN_ALREADY_EXISTS', { name: 'CAMPAIGN_ALREADY_EXISTS' });
                }
            }

            campaignObj.descriptionText = (campaignObj.description ? await Common.convertHtmlToText(campaignObj.description.toString()) : null) as unknown as Text;
            const doExistsByIdInput: CampaignDoExistsByIdDaoInput = { id };
            const exists = await this.campaignDao.doExistsById(doExistsByIdInput, { transaction });
            if (!exists) {
                throw new AppError(404, 'CAMPAIGN_NOT_FOUND', { name: 'CAMPAIGN_NOT_FOUND' });
            }

            if (campaignObj.parentId) {
                // check if parent campaign exists and have same type and its not creating the infinite loop with child
                const parentExistsInput: CampaignDoExistsByIdDaoInput = { id: campaignObj.parentId };
                const parentExists = await this.campaignDao.doExistsById(parentExistsInput, { transaction });
                if (!parentExists) {
                    throw new AppError(404, 'PARENT_CAMPAIGN_NOT_FOUND', { parentId: 'PARENT_CAMPAIGN_NOT_FOUND' });
                } else {
                    if (parentExists.id == id) {
                        throw new AppError(400, 'CATGORY_CANNOT_BE_ASSIGNED_AS_PARENT', { parentId: 'CATGORY_CANNOT_BE_ASSIGNED_AS_PARENT' });
                    }
                    let ifChildIsParent = await this.campaignDao.ifChildIsParent(id, campaignObj.parentId);
                    if (ifChildIsParent) {
                        throw new AppError(400, 'CHILD_CAMPAIGN_CANNOT_BE_ASSIGNED_AS_PARENT', { parentId: 'CHILD_CAMPAIGN_CANNOT_BE_ASSIGNED_AS_PARENT' });
                    }
                }
            }
            if (campaignObj.attachmentId) {
                // check if parent campaign exists and have same type
                const imageExists = await this.attachmentService.verifyFile([campaignObj.attachmentId]);
                if (imageExists.length != 1) {
                    throw new AppError(404, 'ATTACHMENT_NOT_FOUND', { attachmentId: 'ATTACHMENT_NOT_FOUND' });
                }
            }

            const updateCampaignInput: CampaignUpdateDaoInput = { id, campaignObj };
            await this.campaignDao.update(updateCampaignInput, { transaction });
            const getCampaignByIdInput: CampaignGetByIdDaoInput = { id, fullInfo: true };
            let campaign = await this.campaignDao.getCampaignById(getCampaignByIdInput, { transaction });
            await transaction.commit();
            return campaign;

        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // delete the existing campaign
    deleteCampaign = async (data: CampaignDeleteServiceInput): Promise<CampaignObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { id } = data;
            const doExistsByIdInput: CampaignDoExistsByIdDaoInput = { id };
            const exists = await this.campaignDao.doExistsById(doExistsByIdInput, { transaction });
            if (exists) {
                // check if campaign has active child categories
                const hasChildInput: CampaignHasChildDaoInput = { id };
                if (!await this.campaignDao.hasChild(hasChildInput, { transaction })) {
                    const deleteCampaignInput: CampaignDeleteDaoInput = { id };
                    await this.campaignDao.delete(deleteCampaignInput, { transaction });
                    const getCampaignByIdInput: CampaignGetByIdDaoInput = { id, fullInfo: true, paranoid: false };
                    let campaign = await this.campaignDao.getCampaignById(getCampaignByIdInput, { transaction })
                    await transaction.commit();
                    return campaign;
                } else {
                    throw new AppError(400, 'CAMPAIGN_HAS_ACTIVE_CHILD_CATEGORIES', { id: 'CAMPAIGN_HAS_ACTIVE_CHILD_CATEGORIES' });
                }
            }
            throw new AppError(404, 'CAMPAIGN_NOT_FOUND', { name: 'CAMPAIGN_NOT_FOUND' });
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get campaign by id
    getById = async (data: CampaignGetByIdServiceInput): Promise<CampaignObjectInteface> => {
        try {
            const { id, fullInfo = true } = data;
            const getCampaignByIdInput: CampaignGetByIdDaoInput = { id, fullInfo };
            let campaign = await this.campaignDao.getCampaignById(getCampaignByIdInput);
            if (campaign) {
                return campaign;
            }
            throw new AppError(404, 'CAMPAIGN_NOT_FOUND', { name: 'CAMPAIGN_NOT_FOUND' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get campaign by code
    getByCode = async (data: CampaignGetByCodeServiceInput): Promise<CampaignObjectInteface> => {
        try {
            const { code, fullInfo = true } = data;
            const getCampaignByCodeInput: CampaignGetByCodeDaoInput = { code, fullInfo };
            let campaign = await this.campaignDao.getCampaignByCode(getCampaignByCodeInput);
            if (campaign) {
                return campaign;
            }
            throw new AppError(404, 'CAMPAIGN_NOT_FOUND', { name: 'CAMPAIGN_NOT_FOUND' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // update campaign status
    updateStatus = async (data: CampaignUpdateStatusServiceInput): Promise<CampaignObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const { id, status } = data;
            const getCampaignByIdInput: CampaignGetByIdDaoInput = { id };
            let campaign = await this.campaignDao.getCampaignById(getCampaignByIdInput, { transaction });
            if (campaign) {
                const updateCampaignStatusInput: CampaignUpdateStatusDaoInput = { id, status };
                await this.campaignDao.updateStatus(updateCampaignStatusInput, { transaction });
                let campaign = await this.getById({ id, fullInfo: true });
                await transaction.commit();
                return campaign
            } else {
                throw new AppError(404, 'CAMPAIGN_NOT_FOUND', { id: 'CAMPAIGN_NOT_FOUND' });
            }
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list categories
    getCampaigns = async (data: CampaignGetCampaignsServiceInput): Promise<CampaignPaginatedList> => {
        try {
            const { listRequest } = data;
            const { page, perPage, parentId } = listRequest;
            const getCampaignListInput: CampaignGetListDaoInput = { listRequest };
            let campaigns: CampaignPaginatedData = await this.campaignDao.getCampaignList(getCampaignListInput);
            if (campaigns) {
                let totalPages = Common.getTotalPages(campaigns.count, perPage);
                return {
                    data: campaigns.rows,
                    page: page,
                    perPage: perPage,
                    totalRecords: campaigns.count,
                    totalPages: totalPages
                } as unknown as CampaignPaginatedList;
            }
            throw new AppError(400, 'CAMPAIGN_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list all categories
    getAllCampaigns = async (data: CampaignGetAllCampaignsServiceInput): Promise<CampaignObjectSummaryInteface[]> => {
        try {
            const getAllCampaignsInput: CampaignGetAllListDaoInput = { listRequest: data.listRequest };
            let categories = await this.campaignDao.getAllCampaigns(getAllCampaignsInput);
            if (categories) {
                return categories as unknown as CampaignObjectSummaryInteface[];
            }
            throw new AppError(400, 'CAMPAIGN_TYPE_LIST_ERROR_OUT', {});
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list campaign revisions
    getCampaignRevisions = async (data: CampaignGetRevisionsServiceInput): Promise<CampaignPaginatedList> => {
        try {
            const { id, listRequest } = data;
            const { page, perPage } = listRequest
            const getCampaignRevisionListInput: CampaignGetRevisionListDaoInput = { id, listRequest };
            let categories = await this.campaignDao.getCampaignRevisionList(getCampaignRevisionListInput);
            if (categories) {
                let totalPages = Common.getTotalPages(categories.count, perPage);
                return {
                    data: categories.rows,
                    page: page,
                    perPage: perPage,
                    totalRecords: categories.count,
                    totalPages: totalPages
                } as unknown as CampaignPaginatedList;
            }
            throw new AppError(400, 'CAMPAIGN_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: CampaignRestoreRevisionServiceInput): Promise<CampaignObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const restoreRevisionInput: CampaignRestoreRevisionDaoInput = { id: data.id };
            let restoredEntiryId: number = await this.campaignDao.restoreRevision(restoreRevisionInput, { transaction });
            if (restoredEntiryId) {
                const getCampaignByIdInput: CampaignGetByIdDaoInput = { id: restoredEntiryId };
                let campaign: CampaignObjectInteface = await this.campaignDao.getCampaignById(getCampaignByIdInput, { transaction });
                await transaction.commit();
                return campaign;
            }
            throw new AppError(400, 'CAMPAIGN_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);
        }
    }

    // verify campaign
    verifyCampaignId = async (data: CampaignVerifyIdServiceInput): Promise<boolean> => {
        try {
            const verifyCampaignIdInput: CampaignVerifyIdDaoInput = { campaignId: data.campaignId };
            return await this.campaignDao.verifyCampaignId(verifyCampaignIdInput);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get campaign id from code
    getCampaignId = async (data: CampaignGetIdServiceInput): Promise<number> => {
        try {
            const getCampaignIdInput: CampaignGetIdDaoInput = { code: data.code };
            let campaignId = await this.campaignDao.getIdFromCode(getCampaignIdInput);
            return campaignId;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }

    }

    // set campaign sort order
    setSortOrder = async (data: CampaignSetSortOrderServiceInput): Promise<boolean> => {
        const transaction = await sequelize.transaction();
        try {
            const setSortOrderInput: CampaignSetSortOrderDaoInput = { id: data.id, before: data.before, after: data.after };
            let sortOrder: boolean = await this.campaignDao.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);
        }
    }

    // add users to campaign schedule
    addUsersToCampaign = async (data: CampaignAddUsersServiceInput): Promise<CampaignObjectInteface> => {
        const transaction = await sequelize.transaction();
        try {
            const getCampaignByIdInput: CampaignGetByIdDaoInput = { id: data.id };
            let campaign = await this.campaignDao.getCampaignById(getCampaignByIdInput, { transaction });
            if (campaign) {
                const addUsersInCampaignInput: CampaignAddUsersDaoInput = { id: data.id, filters: campaign.filters };
                let addedUsers = await this.campaignDao.addUsersInCampaign(addUsersInCampaignInput, { transaction });
                await transaction.commit();
                return addedUsers;
            } else {
                throw new AppError(404, 'CAMPAIGN_NOT_FOUND', { id: 'CAMPAIGN_NOT_FOUND' });
            }
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list users in campaign schedule
    listCampaignUsers = async (data: CampaignListUsersServiceInput): Promise<UserPaginatedList> => {
        try {
            const { listRequest } = data;
            const { id, parentId, page, perPage, sortBy, sortDirection } = listRequest
            const getUsersInCampaignInput: CampaignGetUsersDaoInput = { listRequest };
            let campaignUsers = await this.campaignDao.getUsersInCampaign(getUsersInCampaignInput);
            if (campaignUsers) {
                let totalPages = Common.getTotalPages(campaignUsers.count, perPage);
                return {
                    data: campaignUsers.rows,
                    page: page,
                    perPage: perPage,
                    totalRecords: campaignUsers.count,
                    totalPages: totalPages
                };
            }
            throw new AppError(404, 'CAMPAIGN_NOT_FOUND', { id: 'CAMPAIGN_NOT_FOUND' });

        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // Campaign scheduling and sending
    prepareCampaign = async () => {
        try {
            await this.campaignDao.getPendingCampaigns();
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    scheduleCampaign = async () => {
        try {
            await this.campaignDao.scheduleCampaign();
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    sendCampaign = async () => {
        try {
            await this.campaignDao.sendCampaing();
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    assignUserCampaign = async () => {
        try {
            await this.campaignDao.assignUserCampaign();
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    generateCampaignReport = async(data: CampaignGenerateReportServiceInput) => {
        try {
            const generateCampaignReportInput: CampaignGenerateReportDaoInput = { campaignId: data.id, isComplete: data.isComplete ?? false };
            const report = await this.campaignDao.generateCampaignReport(generateCampaignReportInput);
            return report;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    getCampaignLogs = async (data: CampaignGetLogsServiceInput) => {
        try {
            const getCampaignLogsInput: CampaignGetLogsDaoInput = { campaignId: data.campaignId, isComplete: data.isComplete, page: data.page, perPage: data.perPage };
            let logs = await this.campaignDao.getCampaignLogs(getCampaignLogsInput);
            if (logs) {
                let totalPages = Common.getTotalPages(logs.count, data.perPage);
                return {
                    data: logs.rows,
                    page: data.page,
                    perPage: data.perPage,
                    totalRecords: logs.count,
                    totalPages: totalPages
                };
            }
            throw new AppError(404, 'LOGS_NOT_FOUND', { id: 'LOGS_NOT_FOUND' });

        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    campaignWhatsappWebhook = async (data: CampaignWhatsappWebhookServiceInput) => {
        const transaction = await sequelize.transaction();
        try {
            const campaignWhatsappWebhookInput: CampaignWhatsappWebhookDaoInput = { requestId: data.requestId, eventName: data.eventName, customerNumber: data.customerNumber };
            let entry = await this.campaignDao.campaignWhatsappWebhook(campaignWhatsappWebhookInput, { transaction });
            if (entry) {
                await transaction.commit();
                return entry;
            }
            throw new AppError(404, 'LOGS_NOT_FOUND', { id: 'LOGS_NOT_FOUND' });

        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }
}
