import { ClientAccountMovementsAdapterMongoose } from "./ClientAccountMovements.Adapter.Mongoose";
import {
    CreateClientAccountMovementParams,
    IClientAccountMovement,
    IClientAccountsManager,
    FindClientAccountMovementsParams,
    DeleteClientAccountMovementParams,
    PaginateClientAccountMovementsParams,
    PaginateClientAccountMovementsResults,
    CalculateClientAccountBalanceParams,
    CLIENT_ACCOUNT_MOVEMENT_TYPES,
} from "./ClientAccountMovements.Interface";
import UsersManager from "../Users/Users";
import { IUserDocument } from "../Users/Users.Adapter.Mongoose";
import ClientsList from "../Clients/Clients";
import CompaniesList from "../Companies/Companies";
import { EmployeeRoles } from "../Employees/Employees.Interface";
import EmployeeManager from "../Employees/Employee";
import { NoPermissionMessage } from "../../helpers/NoPermissionMessage";
import ClientAccount from "../ClientAccounts/ClientAccount";
import { NotificationsManager } from "../Notifications/Notifications";
import { formatCurrency } from "../../helpers/formatCurrency";
import CashFlowBalance from "../CashFlowBalance/CashFlowBalance";
import dayjs from "dayjs";
import { IClient } from "../Clients/Clients.Interface";
import { PAYMENT_PROCCESS_DATE } from "../Payments/Payments.Interface";
import PlanSubscriptionsList from "../PlanSubscriptions/PlanSubscriptons";
import { PlanFeatures } from "../Plans/Plans.interface";
import { NotificationType } from "../../Models/SystemNotifications/SystemNotification.Interface";

type ValidatePermisionsReturnData = {
    companyId: string;
    client: IClient;
};

class ClientAccountMovementsManager implements IClientAccountsManager {
    movements: ClientAccountMovementsAdapterMongoose;

    constructor() {
        this.movements = new ClientAccountMovementsAdapterMongoose();
    }

    private async validateSessionUser(sessionUserId: string): Promise<IUserDocument> {
        const sessionUser = await UsersManager.users.findOne({
            _id: sessionUserId,
        });

        if (!sessionUser) {
            throw new Error(
                "No se ha encontrado el usuario o no tiene permisos para realizar esta acción"
            );
        }

        return sessionUser;
    }

    private async validatePermissions(
        sessionUserId: string,
        clientId: string
    ): Promise<ValidatePermisionsReturnData> {
        const client = await ClientsList.clients.findOne({ _id: clientId });

        if (!client) {
            throw new Error("No se ha encontrado el cliente");
        }

        const clientCompany = await CompaniesList.companies.findOne({
            _id: client.companyId,
        });

        if (!clientCompany) {
            throw new Error("No se ha encontrado la empresa del cliente");
        }

        // Verificar si el usuario de sesión es un empleado con permisos de administrador
        const hasAdminPermission = await EmployeeManager.checkRoleById(
            clientCompany.id,
            sessionUserId,
            EmployeeRoles.ADMIN
        );

        if (!hasAdminPermission) {
            throw new Error(NoPermissionMessage());
        }

        const canAccess = await PlanSubscriptionsList.checkFeature({
            userId: String(clientCompany.ownerId),
            feature: PlanFeatures.PAYMENTS,
        });

        if (!canAccess) {
            throw new Error("La organizacion no tiene un plan que soporte el módulo de pagos");
        }

        return {
            companyId: clientCompany.id,
            client,
        };
    }

    public async calculateBalance(data: CalculateClientAccountBalanceParams): Promise<number> {
        const userSession = await this.validateSessionUser(data.sessionUser);
        await this.validatePermissions(String(userSession.id), data.clientId);

        const movements = await this.movements.find({
            clientId: data.clientId,
        });

        const balance = movements.reduce((acc, movement) => acc + movement.amount, 0);

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

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

        account.amount = balance;

        await account.save();

        return balance;
    }

    public async getBalance(data: FindClientAccountMovementsParams): Promise<number> {
        const userSession = await this.validateSessionUser(data.sessionUser);
        await this.validatePermissions(String(userSession.id), data.clientId);

        const movements = await this.movements.find(data);

        const balance = movements.reduce((acc, movement) => acc + movement.amount, 0);

        return balance;
    }

    private async sendNotification(data: CreateClientAccountMovementParams, balance: number) {
        const client = await ClientsList.clients.findOne({
            _id: data.clientId,
        });

        if (!client) {
            throw new Error("No se encontró el cliente");
        }

        const clientUser = await UsersManager.users.findOne({
            _id: String(client.userId),
        });

        if (!clientUser) {
            return;
        }

        const clientName = ClientsList.getClientFullName(client);
        const subject =
            data.type === CLIENT_ACCOUNT_MOVEMENT_TYPES.DEPOSIT
                ? "Cuenta Corriente - Credito"
                : "Cuenta Corriente - Debito";
        const message = `Hola ${clientName}.  Se ha registrado un nuevo ${
            data.type === CLIENT_ACCOUNT_MOVEMENT_TYPES.DEPOSIT ? "credito" : "debito"
        } de ${formatCurrency(
            data.amount
        )} en tu cuenta corriente. El saldo actual es: ${formatCurrency(balance)}`;

        //Envio la notificacion al cliente.
        await NotificationsManager.sendSystemNotification({
                userId: String(client.userId),
                subject: subject,
                message: message,
                type: NotificationType.BILLING,
                code: String((client as any)._id)
            });
    }

    public async add(data: CreateClientAccountMovementParams): Promise<IClientAccountMovement> {
        const userSession = await this.validateSessionUser(data.sessionUser);
        const { companyId, client } = await this.validatePermissions(
            String(userSession.id),
            data.clientId
        );

        if (data.type === CLIENT_ACCOUNT_MOVEMENT_TYPES.DEPOSIT) {
            data.amount = Math.abs(data.amount);
        } else {
            data.amount = -Math.abs(data.amount);
        }

        const movement = await this.movements.create({
            ...data,
            ...{ createdAt: new Date() },
        });

        const balance = await this.calculateBalance({
            clientId: data.clientId,
            sessionUser: data.sessionUser,
        });

        if (data.notification) {
            this.sendNotification(data, balance);
        }

        if (data.type === CLIENT_ACCOUNT_MOVEMENT_TYPES.DEPOSIT) {
            const clientName = ClientsList.getClientFullName(client);
            const paymentDate = dayjs(new Date());
            const paymentYear = paymentDate.year();
            const paymentMonth = paymentDate.month();
            await CashFlowBalance.createCashFlowMovement({
                amount: data.amount,
                description: "Ingreso en la cuenta del cliente " + clientName,
                year: paymentYear,
                month: paymentMonth,
                companyId: String(companyId),
                clientAccountMovenemtId: String(movement.id),
                clientId: String(data.clientId),
                paymentMethod: data.paymentMethod,
                reference: "",
                type: CLIENT_ACCOUNT_MOVEMENT_TYPES.DEPOSIT,
                proccessDate: PAYMENT_PROCCESS_DATE.NOW,
                sessionUser: data.sessionUser,
            });
        }

        return movement;
    }

    public async paginate(
        data: PaginateClientAccountMovementsParams
    ): Promise<PaginateClientAccountMovementsResults> {
        return await this.movements.paginate(data);
    }

    public async deleteMovement(data: DeleteClientAccountMovementParams): Promise<void> {
        await this.validateSessionUser(data.sessionUser);

        // Primero buscamos el movimiento por su ID
        const movement = await this.movements.findOne({
            id: data.movementId,
            sessionUser: data.sessionUser,
        } as FindClientAccountMovementsParams);

        if (!movement) {
            throw new Error("No se encontró el movimiento");
        }

        const { companyId } = await this.validatePermissions(data.sessionUser, movement.clientId);

        const clientId = movement.clientId;

        await this.movements.delete(data.movementId);

        await this.calculateBalance({
            clientId: clientId,
            sessionUser: data.sessionUser,
        });

        //Si se borra un ingreso de dinero en la cuenta de un cliente, borramos el movimiento en la caja.
        if (movement.type === CLIENT_ACCOUNT_MOVEMENT_TYPES.DEPOSIT) {
            await CashFlowBalance.deleteByClientAccountMovement({
                companyId: String(companyId),
                clientAccountMovementId: data.movementId,
                sessionUser: data.sessionUser,
            });
        }
    }
}

export default new ClientAccountMovementsManager();
