import { Op, WhereOptions } from "sequelize";
import Models from "../models";
import { AppError } from "../../utils/errors";
import Moment from "moment-timezone";
const languageIdentifier = ['id'];

export class LanguageDao {
    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
    }
    // Returns the ID of a language given its code.
    getLanguageId = async (data: LanguageGetLanguageIdDaoInput, options: DaoOptions = {}): Promise<number | false> => {
        try {
            const language: LanguageInterface = await Models.Language.findOne({
                attributes: languageIdentifier,
                where: { code: data.languageCode },
                transaction: options.transaction
            });

            return language?.id || false;
        } catch (err) {
            return false;
        }
    }

    // Returns both the requested language and the default language.
    getDefaultAndCurrentLanguage = async (data: LanguageGetDefaultAndCurrentLanguageDaoInput, options: DaoOptions = {}): Promise<Language[]> => {
        try {
            const languages = await Models.Language.findAll({
                attributes: ['id', 'code', 'isDefault'],
                where: {
                    [Op.or]: [
                        { code: data.languageCode },
                        { isDefault: true }
                    ]
                },
                transaction: options.transaction
            });
            return languages ? JSON.parse(JSON.stringify(languages)) : [];
        } catch (err) {
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    // Returns both the requested language and the default language.
    getRequestedLanguage = async (data: LanguageGetRequestedLanguageDaoInput, options: DaoOptions = {}): Promise<Language | null> => {
        try {
            const languages = await Models.Language.findOne({
                attributes: ['id', 'code', 'isDefault'],
                where: { code: data.languageCode },
                transaction: options.transaction
            });
            return languages ? JSON.parse(JSON.stringify(languages)) : null;
        } catch (err) {
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    getLanguage = async (data: LanguageGetDaoInput, options: DaoOptions = {}): Promise<LanguageResponseObject> => {
        try {
            const { id = null, code = null, paranoid = true } = data;
            const language = await Models.Language.findOne({
                attributes: ['id', 'name', 'code', 'isDefault', 'isDriverEnabled', 'isCustomerEnabled', 'status', 'createdAt', 'updatedAt'],
                where: id ? { id } : code ? { code } : undefined,
                paranoid,
                transaction: options.transaction
            });
            if (!language) {
                throw new AppError(404, 'LANGUAGE_NOT_FOUND', { id: 'LANGUAGE_NOT_FOUND', code: 'LANGUAGE_NOT_FOUND' });
            }
            return JSON.parse(JSON.stringify(language)) as unknown as LanguageResponseObject;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    getLanguageById = async (data: LanguageGetByIdDaoInput, options: DaoOptions = {}): Promise<LanguageResponseObject> => {
        try {
            const getLanguageInput: LanguageGetDaoInput = { id: data.id, code: null, paranoid: data.paranoid ?? true };
            return await this.getLanguage(getLanguageInput, options);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    getLanguageByCode = async (data: LanguageGetByCodeDaoInput, options: DaoOptions = {}): Promise<LanguageResponseObject> => {
        try {
            const getLanguageInput: LanguageGetDaoInput = { id: null, code: data.code, paranoid: data.paranoid ?? true };
            return await this.getLanguage(getLanguageInput, options);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    doExistsById = async (data: LanguageDoExistsByIdDaoInput, options: DaoOptions = {}): Promise<LanguageResponseObject | false> => {
        try {
            const language = await Models.Language.findOne({
                attributes: ['id', 'name', 'code', 'isDefault', 'isDriverEnabled', 'isCustomerEnabled', 'status'],
                where: { id: data.id },
                transaction: options.transaction
            });
            return language ? JSON.parse(JSON.stringify(language)) as unknown as LanguageResponseObject : false;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    doExistsByCode = async (data: LanguageDoExistsByCodeDaoInput, options: DaoOptions = {}): Promise<LanguageResponseObject | false> => {
        try {
            const { code, excludeId = null } = data;
            const language = await Models.Language.findOne({
                attributes: ['id', 'name', 'code', 'isDefault', 'isDriverEnabled', 'isCustomerEnabled', 'status'],
                where: { code, ...(excludeId ? { id: { [Op.ne]: excludeId } } : {}) },
                transaction: options.transaction
            });
            return language ? JSON.parse(JSON.stringify(language)) as unknown as LanguageResponseObject : false;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    create = async (data: LanguageCreateDaoInput, options: DaoOptions = {}): Promise<number> => {
        try {
            const { languageObj } = data;
            if (languageObj.isDefault) {
                await Models.Language.update({ isDefault: false }, { where: {}, transaction: options.transaction });
            }
            const language = await Models.Language.create({
                name: languageObj.name,
                code: languageObj.code,
                isDefault: languageObj.isDefault ?? false,
                isDriverEnabled: languageObj.isDriverEnabled ?? false,
                isCustomerEnabled: languageObj.isCustomerEnabled ?? false,
                status: languageObj.status
            }, { transaction: options.transaction });
            return language.id;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    update = async (data: LanguageUpdateDaoInput, options: DaoOptions = {}): Promise<void> => {
        try {
            const { id, languageObj } = data;
            if (languageObj.isDefault) {
                await Models.Language.update({ isDefault: false }, { where: { id: { [Op.ne]: id } }, transaction: options.transaction });
            }
            await Models.Language.update(languageObj, { where: { id }, transaction: options.transaction });
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

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

    updateEnablement = async (data: LanguageUpdateEnablementDaoInput, options: DaoOptions = {}): Promise<void> => {
        try {
            await Models.Language.update({ isDriverEnabled: data.isDriverEnabled, isCustomerEnabled: data.isCustomerEnabled }, { where: { id: data.id }, transaction: options.transaction });
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    delete = async (data: LanguageDeleteDaoInput, options: DaoOptions = {}): Promise<void> => {
        try {
            const getLanguageByIdInput: LanguageGetByIdDaoInput = { id: data.id };
            const language = await this.getLanguageById(getLanguageByIdInput, options);
            const stamp = Moment().toISOString();
            await Models.Language.update(
                { code: `${language.code}-${stamp}`, name: `${language.name}-${stamp}` },
                { where: { id: data.id }, transaction: options.transaction }
            );
            await Models.Language.destroy({ where: { id: data.id }, transaction: options.transaction });
            return;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    private buildOrderBy = (field: string, direction: string = 'desc') => {
        switch (field) {
            case 'name':
                return [['name', direction]];
            case 'code':
                return [['code', direction]];
            case 'id':
                return [['id', direction]];
            default:
                return [['id', 'ASC']];
        }
    }

    private buildFilter = (where: WhereOptions, searchText: string | null, status: number | null): WhereOptions => {
        const filter: WhereOptions = { ...where };
        if (status !== null && status !== undefined) {
            (filter as any).status = status;
        }
        if (searchText) {
            (filter as any)[Op.or] = [
                { name: { [Op.like]: `%${searchText}%` } },
                { code: { [Op.like]: `%${searchText}%` } }
            ];
        }
        return filter;
    }

    getLanguageList = async (data: LanguageGetListDaoInput, options: DaoOptions = {}): Promise<LanguagePaginatedData> => {
        try {
            const { listRequest } = data;
            const { page, perPage, searchText, status, sortBy, sortDirection } = listRequest;
            const offset = (page - 1) * perPage;
            const where = this.buildFilter({}, searchText, status);
            const languages = await Models.Language.findAndCountAll({
                attributes: ['id', 'name', 'code', 'isDefault', 'isDriverEnabled', 'isCustomerEnabled', 'status', 'createdAt', 'updatedAt'],
                where,
                offset,
                limit: perPage,
                order: this.buildOrderBy(sortBy, sortDirection),
                transaction: options.transaction
            });
            return JSON.parse(JSON.stringify(languages)) as unknown as LanguagePaginatedData;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }

    getAllLanguages = async (data: LanguageGetAllListDaoInput, options: DaoOptions = {}): Promise<LanguageSummaryObject[]> => {
        try {
            const { listRequest } = data;
            const { searchText, status, sortBy, sortDirection } = listRequest;
            const where = this.buildFilter({}, searchText, status);
            const languages = await Models.Language.findAll({
                attributes: ['id', 'name', 'code', 'isDefault', 'isDriverEnabled', 'isCustomerEnabled', 'status', 'createdAt', 'updatedAt'],
                where,
                order: this.buildOrderBy(sortBy, sortDirection),
                limit: +process.env.LIST_ALL_LIMIT!,
                transaction: options.transaction
            });
            return JSON.parse(JSON.stringify(languages)) as unknown as LanguageSummaryObject[];
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_WITH_DAO', err);
        }
    }
}
