import { ClientAccountsAdapterMongoose } from "./ClientAccounts.Adapter.Mongoose";
import {
    CreateClientAccountParams,
    IClientAccount,
    IClientAccountsManager,
    DeleteClientAccountParams,
    FindClientAccountsParams,
    ClientAccountView,
    DeleteClientsAccountsByCompanyParams,
    ClientAccountReport,
    GetClientAccountReportParams,
    GetPrevoiusBalanceParams,
    ClientAccountReportItem,
} from "./ClientAccounts.Interface";
import ClientsList from "../Clients/Clients";
import ClientAccountMovements from "../ClientAccountMovements/ClientAccountMovements";
import { PlanFeatures } from "../Plans/Plans.interface";
import { validatePermissionsByClient, validateSessionUser } from "../../helpers/check";

class ClientAccountManager implements IClientAccountsManager {
    clientAccounts: ClientAccountsAdapterMongoose;

    constructor() {
        this.clientAccounts = new ClientAccountsAdapterMongoose();
    }

    public async deleteClientsAccountsByCompany(
        data: DeleteClientsAccountsByCompanyParams
    ): Promise<void> {
        const clients = await ClientsList.clients.find({
            companyId: data.companyId,
        });

        for (const client of clients) {
            await ClientAccountMovements.movements.movementList.deleteMany({
                clientId: String(client.id),
            });
            await this.clientAccounts.clientAccountList.deleteMany({
                clientId: String(client.id),
            });
        }
    }

    public async createClientAccount(data: CreateClientAccountParams): Promise<IClientAccount> {
        await validateSessionUser({ sessionUser: data.sessionUser });
        await validatePermissionsByClient({
            sessionUser: data.sessionUser,
            feature: PlanFeatures.PAYMENTS,
            clientId: data.clientId,
        });

        const existingAccount = await this.clientAccounts.findOne({
            clientId: data.clientId,
        });

        if (existingAccount) {
            throw new Error("El cliente ya tiene una cuenta registrada");
        }

        return await this.clientAccounts.create(data);
    }

    public async findById(data: FindClientAccountsParams): Promise<IClientAccount> {
        await validateSessionUser({ sessionUser: data.sessionUser });
        await validatePermissionsByClient({
            sessionUser: String(data.sessionUser),
            feature: PlanFeatures.PAYMENTS,
            clientId: data.clientId,
        });

        let account = (await this.clientAccounts.findOne({
            clientId: data.clientId,
        })) as IClientAccount;

        if (!account) {
            account = await this.createClientAccount({
                clientId: data.clientId,
                amount: 0,
                sessionUser: data.sessionUser,
            });
        }

        return account;
    }

    public async deleteClientAccount(data: DeleteClientAccountParams): Promise<void> {
        const { checkCashOnAccount = true } = data;
        await validateSessionUser({ sessionUser: data.sessionUser });

        const account = await this.clientAccounts.findOne({
            clientId: data.clientId,
        });

        if (!account) {
            return;
        }

        await validatePermissionsByClient({
            sessionUser: String(data.sessionUser),
            feature: PlanFeatures.PAYMENTS,
            clientId: data.clientId,
        });

        if (account.amount && account.amount > 0 && checkCashOnAccount) {
            throw new Error(
                "No se puede eliminar la cuenta del cliente ya que tiene saldo positivo."
            );
        }

        //eliminar todos los movimientos de la cuenta
        await ClientAccountMovements.movements.movementList.deleteMany({
            clientId: account.clientId,
        });

        await this.clientAccounts.delete(account.id);
    }

    public async clientAccountView(data: FindClientAccountsParams): Promise<ClientAccountView> {
        await validateSessionUser({ sessionUser: data.sessionUser });
        await validatePermissionsByClient({
            sessionUser: String(data.sessionUser),
            feature: PlanFeatures.PAYMENTS,
            clientId: data.clientId,
        });

        const account = await this.findById({
            clientId: data.clientId,
            sessionUser: data.sessionUser,
        });

        if (!account || !account.id) {
            throw new Error("No se encontró la cuenta del cliente");
        }

        const movements = await ClientAccountMovements.paginate({
            clientId: data.clientId,
            page: 1,
            limit: 10,
            sessionUser: data.sessionUser,
        });

        return {
            id: account.id,
            clientId: account.clientId,
            amount: account.amount,
            lastMovements: movements.data,
        };
    }

    public async getAvailableAmount(data: FindClientAccountsParams): Promise<number> {
        const account = await this.findById(data);
        return account.amount;
    }

    public async getPrevoiusBalance(data: GetPrevoiusBalanceParams): Promise<number> {
        const balance = await ClientAccountMovements.getBalance({
            clientId: data.clientId,
            endDate: data.date,
            sessionUser: data.sessionUser,
        });

        return balance;
    }

    public async getAccountReport(
        data: GetClientAccountReportParams
    ): Promise<ClientAccountReport> {
        await validateSessionUser({ sessionUser: data.sessionUser });
        const { company, client } = await validatePermissionsByClient({
            sessionUser: String(data.sessionUser),
            feature: PlanFeatures.PAYMENTS,
            clientId: data.clientId,
        });

        const previousBalance = await this.getPrevoiusBalance({
            clientId: data.clientId,
            date: data.dateFrom,
            sessionUser: data.sessionUser,
        });

        const movements = await ClientAccountMovements.movements.find({
            clientId: data.clientId,
            startDate: data.dateFrom,
            endDate: data.dateTo,
        }); //falta implementar filtros de fecha

        const dataReport: ClientAccountReportItem[] = movements.map((movement) => {
            return {
                date: movement.createdAt,
                description: movement.description,
                credit: movement.amount > 0 ? movement.amount : 0,
                debit: movement.amount < 0 ? Math.abs(movement.amount) : 0,
                balance: 0, //se calcula luego
            };
        });

        //calcular saldos parciales
        let runningBalance = previousBalance;
        for (let i = 0; i < dataReport.length; i++) {
            runningBalance += dataReport[i].credit;
            runningBalance -= dataReport[i].debit;
            dataReport[i].balance = runningBalance;
        }

        //retornamos el reporte

        return {
            organizationId: String(company.id),
            organizacionName: String(company.name),
            clientId: String(data.clientId),
            clientName: ClientsList.getClientFullName(client),
            balance: previousBalance,
            data: dataReport,
            dateFrom: data.dateFrom,
            dateTo: data.dateTo,
        } as ClientAccountReport;
    }
}

export default new ClientAccountManager();
