import { LanguageDao } from "../dao/language.dao";
import { AppError } from "../../utils/errors";
import { Common } from "../../utils/common";
import { LANGUAGE } from "../config/constants";
import { sequelize } from "../models";

export class LanguageService {
    private accountId: number | null;
    private userId: number | null;
    private language: string;
    private scope: string[] | null;
    private config: userConfig | null;
    private languageDao: LanguageDao;
    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.languageDao = new LanguageDao({
            userId: this.userId,
            accountId: this.accountId,
            language: this.language,
            scope: this.scope,
            config: this.config
        });
    }
    // Returns the requested and default language objects based on input language code.
    getRequestedAndDefaultLanguage = async (): Promise<LanguageInfo> => {
        try {
            const getDefaultAndCurrentLanguageInput: LanguageGetDefaultAndCurrentLanguageDaoInput = { languageCode: this.language };
            const languages: Language[] = await this.languageDao.getDefaultAndCurrentLanguage(getDefaultAndCurrentLanguageInput);
            if (!languages || languages.length === 0) {
                throw new Error("Languages not seeded in the database");
            }
            const defaultLang = languages.find(lang => lang.isDefault);
            if (!defaultLang) {
                throw new Error("Default language not defined");
            }
            const requestedLang = languages.find(
                lang => lang.code.toLowerCase() === this.language?.toLowerCase()
            ) || defaultLang;

            return {
                requested: requestedLang,
                default: defaultLang
            };
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_LANGUAGE_SERVICE', err);
        }
    }

    getRequestedLanguage = async (): Promise<LanguageInfo> => {
        try {
            const getRequestedLanguageInput: LanguageGetRequestedLanguageDaoInput = { languageCode: this.language };
            const requestedLang: Language | null = await this.languageDao.getRequestedLanguage(getRequestedLanguageInput);
            if (!requestedLang) {
                throw new Error("Languages not seeded in the database");
            }
            return {
                requested: requestedLang
            };
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_LANGUAGE_SERVICE', err);
        }
    }

    // Returns the language ID given a language code.
    getLanguageId = async (data: LanguageGetLanguageIdServiceInput): Promise<number> => {
        try {
            const getLanguageIdInput: LanguageGetLanguageIdDaoInput = { languageCode: data.languageCode };
            const language = await this.languageDao.getLanguageId(getLanguageIdInput);
            if (language) {
                return language
            } else {
                throw new AppError(400, 'ERROR_WHILE_GETTING_THE_REQUESTED_LANGUAGE', { languageCode: "ERROR_WHILE_GETTING_THE_REQUESTED_LANGUAGE" });
            }
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_LANGUAGE_SERVICE', err);
        }
    }

    createLanguage = async (data: LanguageCreateServiceInput): Promise<LanguageResponseObject> => {
        const transaction = await sequelize.transaction();
        try {
            const { name, code: inputCode, isDefault, isDriverEnabled, isCustomerEnabled, status } = data;
            const code = inputCode.trim().toLowerCase();
            const doExistsByCodeInput: LanguageDoExistsByCodeDaoInput = { code };
            const exists = await this.languageDao.doExistsByCode(doExistsByCodeInput, { transaction });
            if (exists) {
                throw new AppError(400, 'LANGUAGE_ALREADY_EXISTS', { code: 'LANGUAGE_ALREADY_EXISTS' });
            }
            const languageObj: LanguageObject = {
                name,
                code,
                isDefault: isDefault ?? false,
                isDriverEnabled: isDriverEnabled ?? false,
                isCustomerEnabled: isCustomerEnabled ?? false,
                status: status ?? LANGUAGE.STATUS.ACTIVE
            };
            const createLanguageInput: LanguageCreateDaoInput = { languageObj };
            const id = await this.languageDao.create(createLanguageInput, { transaction });
            const getLanguageByIdInput: LanguageGetByIdDaoInput = { id };
            const language = await this.languageDao.getLanguageById(getLanguageByIdInput, { transaction });
            await transaction.commit();
            return language;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_LANGUAGE_SERVICE', err);
        }
    }

    updateLanguage = async (data: LanguageUpdateServiceInput): Promise<LanguageResponseObject> => {
        const transaction = await sequelize.transaction();
        try {
            const { id, name, code: inputCode, isDefault, isDriverEnabled, isCustomerEnabled, status } = data;
            const doExistsByIdInput: LanguageDoExistsByIdDaoInput = { id };
            const exists = await this.languageDao.doExistsById(doExistsByIdInput, { transaction });
            if (!exists) {
                throw new AppError(404, 'LANGUAGE_NOT_FOUND', { id: 'LANGUAGE_NOT_FOUND' });
            }
            const code = inputCode.trim().toLowerCase();
            const codeInUseInput: LanguageDoExistsByCodeDaoInput = { code, excludeId: id };
            const codeInUse = await this.languageDao.doExistsByCode(codeInUseInput, { transaction });
            if (codeInUse) {
                throw new AppError(400, 'LANGUAGE_ALREADY_EXISTS', { code: 'LANGUAGE_ALREADY_EXISTS' });
            }
            if (exists.isDefault && isDefault === false) {
                throw new AppError(400, 'DEFAULT_LANGUAGE_CANNOT_BE_UNSET', { isDefault: 'DEFAULT_LANGUAGE_CANNOT_BE_UNSET' });
            }
            const languageObj: LanguageObject = {
                name,
                code,
                isDefault: isDefault ?? exists.isDefault,
                isDriverEnabled: isDriverEnabled ?? exists.isDriverEnabled,
                isCustomerEnabled: isCustomerEnabled ?? exists.isCustomerEnabled,
                status: status ?? exists.status
            };
            const updateLanguageInput: LanguageUpdateDaoInput = { id, languageObj };
            await this.languageDao.update(updateLanguageInput, { transaction });
            const getLanguageByIdInput: LanguageGetByIdDaoInput = { id };
            const language = await this.languageDao.getLanguageById(getLanguageByIdInput, { transaction });
            await transaction.commit();
            return language;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_LANGUAGE_SERVICE', err);
        }
    }

    updateEnablement = async (data: LanguageUpdateEnablementServiceInput): Promise<LanguageResponseObject> => {
        const transaction = await sequelize.transaction();
        try {
            const { id, isDriverEnabled, isCustomerEnabled } = data;
            const doExistsByIdInput: LanguageDoExistsByIdDaoInput = { id };
            const exists = await this.languageDao.doExistsById(doExistsByIdInput, { transaction });
            if (!exists) {
                throw new AppError(404, 'LANGUAGE_NOT_FOUND', { id: 'LANGUAGE_NOT_FOUND' });
            }
            const updateEnablementInput: LanguageUpdateEnablementDaoInput = { id, isDriverEnabled, isCustomerEnabled };
            await this.languageDao.updateEnablement(updateEnablementInput, { transaction });
            const getLanguageByIdInput: LanguageGetByIdDaoInput = { id };
            const language = await this.languageDao.getLanguageById(getLanguageByIdInput, { transaction });
            await transaction.commit();
            return language;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_LANGUAGE_SERVICE', err);
        }
    }

    updateStatus = async (data: LanguageUpdateStatusServiceInput): Promise<LanguageResponseObject> => {
        const transaction = await sequelize.transaction();
        try {
            const { id, status } = data;
            const doExistsByIdInput: LanguageDoExistsByIdDaoInput = { id };
            const exists = await this.languageDao.doExistsById(doExistsByIdInput, { transaction });
            if (!exists) {
                throw new AppError(404, 'LANGUAGE_NOT_FOUND', { id: 'LANGUAGE_NOT_FOUND' });
            }
            if (exists.isDefault && status === LANGUAGE.STATUS.INACTIVE) {
                throw new AppError(400, 'DEFAULT_LANGUAGE_CANNOT_BE_DEACTIVATED', { status: 'DEFAULT_LANGUAGE_CANNOT_BE_DEACTIVATED' });
            }
            const updateStatusInput: LanguageUpdateStatusDaoInput = { id, status };
            await this.languageDao.updateStatus(updateStatusInput, { transaction });
            const getLanguageByIdInput: LanguageGetByIdDaoInput = { id };
            const language = await this.languageDao.getLanguageById(getLanguageByIdInput, { transaction });
            await transaction.commit();
            return language;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_LANGUAGE_SERVICE', err);
        }
    }

    deleteLanguage = async (data: LanguageDeleteServiceInput): Promise<LanguageResponseObject> => {
        const transaction = await sequelize.transaction();
        try {
            const { id } = data;
            const doExistsByIdInput: LanguageDoExistsByIdDaoInput = { id };
            const exists = await this.languageDao.doExistsById(doExistsByIdInput, { transaction });
            if (!exists) {
                throw new AppError(404, 'LANGUAGE_NOT_FOUND', { id: 'LANGUAGE_NOT_FOUND' });
            }
            if (exists.isDefault) {
                throw new AppError(400, 'DEFAULT_LANGUAGE_CANNOT_BE_DELETED', { id: 'DEFAULT_LANGUAGE_CANNOT_BE_DELETED' });
            }
            const deleteLanguageInput: LanguageDeleteDaoInput = { id };
            await this.languageDao.delete(deleteLanguageInput, { transaction });
            const getLanguageByIdInput: LanguageGetByIdDaoInput = { id, paranoid: false };
            const language = await this.languageDao.getLanguageById(getLanguageByIdInput, { transaction });
            await transaction.commit();
            return language;
        } catch (err) {
            await transaction.rollback();
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_LANGUAGE_SERVICE', err);
        }
    }

    getById = async (data: LanguageGetByIdServiceInput): Promise<LanguageResponseObject> => {
        try {
            const getLanguageByIdInput: LanguageGetByIdDaoInput = { id: data.id };
            return await this.languageDao.getLanguageById(getLanguageByIdInput);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_LANGUAGE_SERVICE', err);
        }
    }

    getByCode = async (data: LanguageGetByCodeServiceInput): Promise<LanguageResponseObject> => {
        try {
            const getLanguageByCodeInput: LanguageGetByCodeDaoInput = { code: data.code.trim().toLowerCase() };
            return await this.languageDao.getLanguageByCode(getLanguageByCodeInput);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_LANGUAGE_SERVICE', err);
        }
    }

    getLanguages = async (data: LanguageGetLanguagesServiceInput): Promise<LanguagePaginatedList> => {
        try {
            const { listRequest } = data;
            const { page, perPage, searchText, sortBy, sortDirection, status } = listRequest;
            const normalizedListRequest: LanguageListRequestObject = { page, perPage, searchText, sortBy, sortDirection, status };
            const getLanguageListInput: LanguageGetListDaoInput = { listRequest: normalizedListRequest };
            const languages = await this.languageDao.getLanguageList(getLanguageListInput);
            return {
                data: languages.rows,
                page,
                perPage,
                totalRecords: languages.count,
                totalPages: Common.getTotalPages(languages.count, perPage)
            } as unknown as LanguagePaginatedList;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_LANGUAGE_SERVICE', err);
        }
    }

    getAllLanguages = async (data: LanguageGetAllLanguagesServiceInput): Promise<LanguageSummaryObject[]> => {
        try {
            const { searchText, sortBy, sortDirection, status } = data.listRequest;
            const normalizedListRequest: LanguageListAllRequestObject = { searchText, sortBy, sortDirection, status };
            const getAllLanguagesInput: LanguageGetAllListDaoInput = { listRequest: normalizedListRequest };
            return await this.languageDao.getAllLanguages(getAllLanguagesInput);
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_LANGUAGE_SERVICE', err);
        }
    }
}
