import { ActivityDao } from "../dao/activity.dao";
import { LanguageService } from "../services/language.service";
import { Common } from "../../utils/common";
import { AppError } from "../../utils/errors";
import axios from 'axios';
import { ACTIVITY } from "../config/constants";
import { sequelize } from "../models";

export class ActivityService {
    private accountId: number | null;
    private userId: number | null;
    private language: string;
    private scope: string[] | null;
    private config: userConfig | null;
    private activityDao: ActivityDao;
    private languageService: LanguageService;
    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.activityDao = new ActivityDao({
            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
        });
    }

    // create a new activity
    createActivity = async (input: ActivityCreateServiceInput): Promise<ActivityObjectInteface> => {
        try {
            const activityReplacements = input.description ? Common.fetchShortCodes(input.description as unknown as string) : null;
            const replacements = activityReplacements ? activityReplacements.join(',') : null;
            let activityObj: ActivityObject = { code: input.code, status: input.status, replacements };
            activityObj.status = activityObj.status ?? ACTIVITY.STATUS.ACTIVE;
            let activityContentObj: ActivityContentObject = { title: input.title, description: input.description };
            const languages: LanguageInfo = await this.languageService.getRequestedAndDefaultLanguage();
            activityObj.code = Common.slugify(activityObj.code);
            activityContentObj.descriptionText = (activityContentObj.description ? await Common.convertHtmlToText(activityContentObj.description.toString()) : null) as unknown as Text
            const doExistsByCodeDaoInput: ActivityDoExistsByCodeDaoInput = { code: activityObj.code };
            const exists = await this.activityDao.doExistsByCode(doExistsByCodeDaoInput, {});
            if (!exists) {
                const transaction = await sequelize.transaction();
                let activityIdentifier: number;
                try {
                    const createDaoInput: ActivityCreateDaoInput = { activityObj, activityContentObj, languages };
                    activityIdentifier = await this.activityDao.create(createDaoInput, { transaction });
                    const setSortOrderDaoInput: ActivitySetSortOrderDaoInput = { id: activityIdentifier };
                    await this.activityDao.setSortOrder(setSortOrderDaoInput, { transaction });
                    await transaction.commit();
                } catch (err) {
                    await transaction.rollback();
                    throw err;
                }
                const getActivityByIdDaoInput: ActivityGetByIdDaoInput = { id: activityIdentifier };
                let activity: ActivityObjectInteface = await this.activityDao.getActivityById(getActivityByIdDaoInput, {});
                return activity;
            }
            throw new AppError(400, 'ACTIVITY_ALREADY_EXISTS', { name: 'ACTIVITY_ALREADY_EXISTS' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // update the existing activity
    updateActivity = async (input: ActivityUpdateServiceInput): Promise<ActivityObjectInteface> => {
        try {
            const id = input.id;
            const activityReplacements = input.description ? Common.fetchShortCodes(input.description as unknown as string) : null;
            const replacements = activityReplacements ? activityReplacements.join(',') : null;
            let activityObj: ActivityObject = { code: input.code, status: input.status, replacements };
            activityObj.status = activityObj.status ?? ACTIVITY.STATUS.ACTIVE;
            let activityContentObj: ActivityContentObject = { title: input.title, description: input.description };
            const languages: LanguageInfo = await this.languageService.getRequestedLanguage();
            activityContentObj.descriptionText = (activityContentObj.description ? await Common.convertHtmlToText(activityContentObj.description.toString()) : null) as unknown as Text;
            activityObj.code = Common.slugify(activityObj.code);
            const doExistsByIdDaoInput: ActivityDoExistsByIdDaoInput = { id };
            const exists = await this.activityDao.doExistsById(doExistsByIdDaoInput, {});
            if (exists) {
                if (activityObj.code) {
                    const doExistsByCodeDaoInput: ActivityDoExistsByCodeDaoInput = { code: activityObj.code, excludeId: id };
                    let codeInUse = await this.activityDao.doExistsByCode(doExistsByCodeDaoInput, {});
                    if (codeInUse) {
                        throw new AppError(400, 'ACTIVITY_CODE_ALREADY_EXISTS', { code: 'ACTIVITY_CODE_ALREADY_EXISTS' });
                    }
                }
                const transaction = await sequelize.transaction();
                try {
                    const updateDaoInput: ActivityUpdateDaoInput = { id, activityObj, activityContentObj, languages };
                    await this.activityDao.update(updateDaoInput, { transaction });
                    await transaction.commit();
                } catch (err) {
                    await transaction.rollback();
                    throw err;
                }
                const getActivityByIdDaoInput: ActivityGetByIdDaoInput = { id };
                let activity: ActivityObjectInteface = await this.activityDao.getActivityById(getActivityByIdDaoInput, {});
                return activity;
            }
            throw new AppError(404, 'ACTIVITY_NOT_FOUND', { name: 'ACTIVITY_NOT_FOUND' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // delete the existing activity
    deleteActivity = async (input: ActivityDeleteServiceInput): Promise<ActivityObjectInteface> => {
        try {
            const doExistsByIdDaoInput: ActivityDoExistsByIdDaoInput = { id: input.id };
            const exists = await this.activityDao.doExistsById(doExistsByIdDaoInput, {});
            if (exists) {
                const transaction = await sequelize.transaction();
                try {
                    const deleteDaoInput: ActivityDeleteDaoInput = { id: input.id };
                    await this.activityDao.delete(deleteDaoInput, { transaction });
                    await transaction.commit();
                } catch (err) {
                    await transaction.rollback();
                    throw err;
                }
                const getActivityByIdDaoInput: ActivityGetByIdDaoInput = { id: input.id, expanded: true, paranoid: false };
                let activity: ActivityObjectInteface = await this.activityDao.getActivityById(getActivityByIdDaoInput, {})
                return activity;
            } else {
                throw new AppError(404, 'ACTIVITY_NOT_FOUND', { name: 'ACTIVITY_NOT_FOUND' });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get activity by id
    getById = async (input: ActivityGetByIdServiceInput): Promise<ActivityObjectInteface> => {
        try {
            const getActivityByIdDaoInput: ActivityGetByIdDaoInput = { id: input.id, expanded: input.expanded ?? true };
            let activity: ActivityObjectInteface = await this.activityDao.getActivityById(getActivityByIdDaoInput, {});
            return activity;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get activity by code
    getByCode = async (input: ActivityGetByCodeServiceInput): Promise<ActivityObjectInteface> => {
        try {
            const getActivityByCodeDaoInput: ActivityGetByCodeDaoInput = { code: input.code, expanded: input.expanded ?? true };
            let activity: ActivityObjectInteface = await this.activityDao.getActivityByCode(getActivityByCodeDaoInput, {});
            return activity;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list activities
    getActivities = async (input: ActivityGetActivitiesServiceInput): Promise<ActivityPaginatedList> => {
        try {
            const { listRequest } = input;
            const { page, perPage } = listRequest
            const getActivityListDaoInput: ActivityGetListDaoInput = { listRequest };
            let activities: ActivityPaginatedData = await this.activityDao.getActivityList(getActivityListDaoInput, {});
            let totalPages = Common.getTotalPages(activities.count, perPage);
            return {
                data: activities.rows,
                page: page,
                perPage: perPage,
                totalRecords: activities.count,
                totalPages: totalPages
            } as unknown as ActivityPaginatedList;

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

    // list all activities
    getAllActivities = async (input: ActivityGetAllActivitiesServiceInput): Promise<ActivityObjectSummaryInteface[]> => {
        try {
            const getAllActivitiesDaoInput: ActivityGetAllListDaoInput = { listRequest: input.listRequest };
            let activities: ActivityObjectSummaryInteface[] = await this.activityDao.getAllActivities(getAllActivitiesDaoInput, {}) as ActivityObjectSummaryInteface[];
            return activities;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list activity revisions
    getActivityRevisions = async (input: ActivityGetRevisionsServiceInput): Promise<ActivityPaginatedList> => {
        try {
            const { id, listRequest } = input;
            const { page, perPage } = listRequest
            const getActivityRevisionListDaoInput: ActivityGetRevisionListDaoInput = { id, listRequest };
            let activities: ActivityPaginatedData = await this.activityDao.getActivityRevisionList(getActivityRevisionListDaoInput, {});
            let totalPages = Common.getTotalPages(activities.count, perPage);
            return {
                data: activities.rows,
                page: page,
                perPage: perPage,
                totalRecords: activities.count,
                totalPages: totalPages
            } as unknown as ActivityPaginatedList;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // Restore revision
    restoreRevision = async (input: ActivityRestoreRevisionServiceInput): Promise<ActivityObjectInteface> => {
        try {
            const transaction = await sequelize.transaction();
            let restoredEntiryId: number;
            try {
                const restoreRevisionDaoInput: ActivityRestoreRevisionDaoInput = { id: input.id };
                restoredEntiryId = await this.activityDao.restoreRevision(restoreRevisionDaoInput, { transaction });
                await transaction.commit();
            } catch (err) {
                await transaction.rollback();
                throw err;
            }
            const getActivityByIdDaoInput: ActivityGetByIdDaoInput = { id: restoredEntiryId };
            let activity = await this.activityDao.getActivityById(getActivityByIdDaoInput, {});
            return activity;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get activity id from code
    getActivityId = async (input: ActivityGetIdServiceInput): Promise<number> => {
        try {
            const getIdFromCodeDaoInput: ActivityGetIdFromCodeDaoInput = { code: input.code };
            let activityId = await this.activityDao.getIdFromCode(getIdFromCodeDaoInput, {});
            return activityId;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get activity id from code
    logUserActivity = async (input: ActivityLogUserActivityServiceInput): Promise<void> => {
        try {
            const location = input.storeLocation ? (await axios.get(`https://ipapi.co/${input.ip}/json/`)).data : null;
            const logUserActivityDaoInput: ActivityLogUserActivityDaoInput = {
                code: input.code,
                ip: input.ip,
                replacements: input.replacements,
                deviceInfo: input.deviceInfo,
                location,
                forUser: input.forUser ?? null
            };
            await this.activityDao.logUserActivity(logUserActivityDaoInput, {});
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // set sort order
    setSortOrder = async (input: ActivitySetSortOrderServiceInput): Promise<boolean> => {
        try {
            const transaction = await sequelize.transaction();
            let sortOrder: boolean;
            try {
                const setSortOrderDaoInput: ActivitySetSortOrderDaoInput = { id: input.id, before: input.before, after: input.after };
                sortOrder = await this.activityDao.setSortOrder(setSortOrderDaoInput, { transaction });
                await transaction.commit();
            } catch (err) {
                await transaction.rollback();
                throw err;
            }
            return sortOrder;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // update activity status
    updateStatus = async (input: ActivityUpdateStatusServiceInput): Promise<ActivityObjectInteface> => {
        try {
            const getActivityByIdDaoInput: ActivityGetByIdDaoInput = { id: input.id };
            let activity = await this.activityDao.getActivityById(getActivityByIdDaoInput, {});
            if (activity) {
                const updateStatusDaoInput: ActivityUpdateStatusDaoInput = { id: input.id, status: input.status };
                await this.activityDao.updateStatus(updateStatusDaoInput, {});
                const getByIdInput: ActivityGetByIdServiceInput = { id: input.id, expanded: true };
                let activityObject = await this.getById(getByIdInput);
                return activityObject
            } else {
                throw new AppError(404, 'ACTIVITY_NOT_FOUND', { id: 'ACTIVITY_NOT_FOUND' });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }
}
