import { AccountDao } from "../dao/account.dao";
import { LanguageService } from "../services/language.service";
import { Common } from "../../utils/common";
import { AppError } from "../../utils/errors";
import { sequelize } from "../models";
import { ACCOUNT } from "../config/constants";

export class AccountService {
    private accountId: number | null;
    private userId: number | null;
    private language: string;
    private scope: string[] | null;
    private config: userConfig | null;
    private accountDao: AccountDao;
    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.accountDao = new AccountDao({
            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 account
    createAccount = async (input: AccountCreateServiceInput): Promise<AccountObjectInteface> => {
        try {
            const accountObj: AccountObject = { code: '-', name: input.name, key: input.key, status: input.status ?? ACCOUNT.STATUS.ACTIVE };
            const code = Common.slugify(accountObj.name);
            const doExistsByCodeInput: AccountDoExistsByCodeDaoInput = { code };
            const exists = await this.accountDao.doExistsByCode(doExistsByCodeInput, {});
            if (!exists) {
                accountObj.code = code;
                const transaction = await sequelize.transaction();
                let accountIdentifier: number;
                try {
                    const createDaoInput: AccountCreateDaoInput = { accountObj };
                    accountIdentifier = await this.accountDao.create(createDaoInput, { transaction });
                    const setSortOrderDaoInput: AccountSetSortOrderDaoInput = { id: accountIdentifier };
                    await this.accountDao.setSortOrder(setSortOrderDaoInput, { transaction });
                    await transaction.commit();
                } catch (err) {
                    await transaction.rollback();
                    throw err;
                }
                const getAccountByIdDaoInput: AccountGetByIdDaoInput = { id: accountIdentifier };
                let account: AccountObjectInteface = await this.accountDao.getAccountById(getAccountByIdDaoInput, {});
                return account;
            }
            throw new AppError(400, 'ACCOUNT_ALREADY_EXISTS', { name: 'ACCOUNT_ALREADY_EXISTS' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // update the existing account
    updateAccount = async (input: AccountUpdateServiceInput): Promise<AccountObjectInteface> => {
        try {
            const id = input.id;
            const accountObj: AccountObject = { code: '-', name: input.name, key: input.key, status: input.status ?? ACCOUNT.STATUS.ACTIVE };
            const code = Common.slugify(accountObj.name);
            const doExistsByIdInput: AccountDoExistsByIdDaoInput = { id };
            const exists = await this.accountDao.doExistsById(doExistsByIdInput, {});
            if (exists) {
                if (code) {
                    const doExistsByCodeInput: AccountDoExistsByCodeDaoInput = { code, excludeId: id };
                    let codeInUse = await this.accountDao.doExistsByCode(doExistsByCodeInput, {});
                    if (codeInUse) {
                        throw new AppError(400, 'ACCOUNT_ALREADY_EXISTS', { name: 'ACCOUNT_ALREADY_EXISTS' });
                    }
                }
                accountObj.code = code;
                const transaction = await sequelize.transaction();
                try {
                    const updateDaoInput: AccountUpdateDaoInput = { id, accountObj };
                    await this.accountDao.update(updateDaoInput, { transaction });
                    await transaction.commit();
                } catch (err) {
                    await transaction.rollback();
                    throw err;
                }
                const getAccountByIdDaoInput: AccountGetByIdDaoInput = { id };
                let account: AccountObjectInteface = await this.accountDao.getAccountById(getAccountByIdDaoInput, {});
                return account;
            }
            throw new AppError(404, 'ACCOUNT_NOT_FOUND', { name: 'ACCOUNT_NOT_FOUND' });
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // delete the existing account
    deleteAccount = async (input: AccountDeleteServiceInput): Promise<AccountObjectInteface> => {
        try {
            const transaction = await sequelize.transaction();
            try {
                const deleteDaoInput: AccountDeleteDaoInput = { id: input.id };
                await this.accountDao.delete(deleteDaoInput, { transaction });
                await transaction.commit();
            } catch (err) {
                await transaction.rollback();
                throw err;
            }
            const getAccountByIdDaoInput: AccountGetByIdDaoInput = { id: input.id, expanded: true, paranoid: false };
            let account: AccountObjectInteface = await this.accountDao.getAccountById(getAccountByIdDaoInput, {})
            return account;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get category Type by id
    getById = async (input: AccountGetByIdServiceInput): Promise<AccountObjectInteface> => {
        try {
            const getAccountByIdDaoInput: AccountGetByIdDaoInput = { id: input.id, expanded: input.expanded ?? true };
            let account: AccountObjectInteface = await this.accountDao.getAccountById(getAccountByIdDaoInput, {});
            return account;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get category Type by code
    getByCode = async (input: AccountGetByCodeServiceInput): Promise<AccountObjectInteface> => {
        try {
            const getAccountByCodeDaoInput: AccountGetByCodeDaoInput = { code: input.code, expanded: input.expanded ?? true };
            let account: AccountObjectInteface = await this.accountDao.getAccountByCode(getAccountByCodeDaoInput, {});
            return account;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list accounts
    getAccounts = async (input: AccountGetAccountsServiceInput): Promise<AccountPaginatedList> => {
        try {
            const { listRequest } = input;
            const { page, perPage } = listRequest;
            const getAccountListDaoInput: AccountGetListDaoInput = { listRequest };
            let accounts: AccountPaginatedData = await this.accountDao.getAccountList(getAccountListDaoInput, {});
            let totalPages = Common.getTotalPages(accounts.count, perPage);
            return {
                data: accounts.rows,
                page: page,
                perPage: perPage,
                totalRecords: accounts.count,
                totalPages: totalPages
            } as unknown as AccountPaginatedList;

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

    // list all accounts
    getAllAccounts = async (input: AccountGetAllAccountsServiceInput): Promise<AccountObjectSummaryInteface[]> => {
        try {
            const getAllAccountsDaoInput: AccountGetAllListDaoInput = { listRequest: input.listRequest };
            let accounts: AccountObjectSummaryInteface[] = await this.accountDao.getAllAccounts(getAllAccountsDaoInput, {});
            return accounts;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // list accounts revisions
    getAccountsRevisions = async (input: AccountGetRevisionsServiceInput): Promise<AccountPaginatedList> => {
        try {
            const { id, listRequest, language = process.env.DEFAULT_LANGUAGE_CODE! } = input;
            const { page, perPage } = listRequest;
            const getAccountRevisionListDaoInput: AccountGetRevisionListDaoInput = { id, listRequest, language };
            let accounts: AccountPaginatedData = await this.accountDao.getAccountRevisionList(getAccountRevisionListDaoInput, {});
            let totalPages = Common.getTotalPages(accounts.count, perPage);
            return {
                data: accounts.rows,
                page: page,
                perPage: perPage,
                totalRecords: accounts.count,
                totalPages: totalPages
            } as unknown as AccountPaginatedList;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // Restore account revision
    restoreRevision = async (input: AccountRestoreRevisionServiceInput): Promise<AccountObjectInteface> => {
        try {
            const transaction = await sequelize.transaction();
            let restoredEntiryId: number;
            try {
                const restoreRevisionDaoInput: AccountRestoreRevisionDaoInput = { id: input.id };
                restoredEntiryId = await this.accountDao.restoreRevision(restoreRevisionDaoInput, { transaction });
                await transaction.commit();
            } catch (err) {
                await transaction.rollback();
                throw err;
            }
            const getAccountByIdDaoInput: AccountGetByIdDaoInput = { id: restoredEntiryId };
            let account = await this.accountDao.getAccountById(getAccountByIdDaoInput, {});
            return account;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // get account id from code
    getAccountId = async (input: AccountGetIdServiceInput): Promise<number> => {
        try {
            const getIdFromCodeDaoInput: AccountGetIdFromCodeDaoInput = { code: input.code };
            let categoryId = await this.accountDao.getIdFromCode(getIdFromCodeDaoInput, {});
            return categoryId;
        } catch (err) {
            if (err instanceof AppError) { throw err; }
            throw new AppError(500, 'SOMETHING_WENT_WRONG_IN_SERVICE', err);
        }
    }

    // update account sort order
    setSortOrder = async (input: AccountSetSortOrderServiceInput): Promise<boolean> => {
        try {
            const transaction = await sequelize.transaction();
            let sortOrder: boolean;
            try {
                const setSortOrderDaoInput: AccountSetSortOrderDaoInput = { id: input.id, before: input.before, after: input.after };
                sortOrder = await this.accountDao.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);
        }
    }
}
