import UsersManager from "../Users/Users";
import { IUserDocument } from "../Users/Users.Adapter.Mongoose";
import CompaniesList from "../Companies/Companies";
import { EmployeeRoles } from "../Employees/Employees.Interface";
import EmployeeManager from "../Employees/Employee";
import { NoPermissionMessage } from "../../helpers/NoPermissionMessage";
import {
    CalculateCashFlowBalanceParams,
    CashFlowHistoryView,
    FindCashFlowBalanceParams,
    ICashFlowBalance,
    ICashFlowBalanceManager,
} from "./CashFlowBalance.Interface";
import { CashFlowBalanceAdapterMongoose } from "./CashFlowBalance.Adapter.Mongoose";
import dayjs from "dayjs";
import CashFlow from "../CashFlow/CashFlow";
import {
    CreateCashFlowParams,
    DeleteByClientAccountMovementParams,
    DeleteMovementByPaymentParams,
    DeleteMovementParams,
} from "../CashFlow/CashFlow.Interface";
import Payments from "../Payments/Payments";
import ClientAccountMovements from "../ClientAccountMovements/ClientAccountMovements";
import { CLIENT_ACCOUNT_MOVEMENT_TYPES } from "../ClientAccountMovements/ClientAccountMovements.Interface";
import PlanSubscriptionsList from "../PlanSubscriptions/PlanSubscriptons";
import { PlanFeatures } from "../Plans/Plans.interface";

class CashFlowBalanceManager implements ICashFlowBalanceManager {
    balance: CashFlowBalanceAdapterMongoose;

    constructor() {
        this.balance = new CashFlowBalanceAdapterMongoose();
    }

    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, companyId: string): Promise<void> {
        const company = await CompaniesList.companies.findOne({
            _id: companyId,
        });

        if (!company) {
            throw new Error("No se ha encontrado la organización");
        }

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

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

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

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

    public async createCashFlowMovement(data: CreateCashFlowParams): Promise<void> {
        const userSession = await this.validateSessionUser(data.sessionUser);
        await this.validatePermissions(String(userSession.id), data.companyId);

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

        const movement = await CashFlow.movements.create(data);

        const movDate = dayjs(movement.createdAt);
        const movDateYear = movDate.year();
        const movDateMonth = movDate.month();

        console.log("calculando balance:", {
            year: movDateYear,
            month: movDateMonth,
            companyId: data.companyId,
            sessionUser: data.sessionUser,
        });

        await this.calculateBalance({
            year: movDateYear,
            month: movDateMonth,
            companyId: data.companyId,
            sessionUser: data.sessionUser,
        });
    }

    private async getBalanceByYearMonth(
        companyId: string,
        year: number,
        month: number
    ): Promise<number> {
        const movements = await CashFlow.movements.find({
            companyId: companyId,
            month: month,
            year: year,
        });

        return movements.reduce((acc, movement) => acc + movement.amount, 0);
    }

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

        const balance = await this.balance.findOne({
            companyId: data.companyId,
            year: data.year,
            month: data.month,
        });

        if (!balance) {
            const amount = await this.getBalanceByYearMonth(data.companyId, data.year, data.month);
            return await this.balance.balanceList.create({
                amount,
                companyId: data.companyId,
                year: data.year,
                month: data.month,
                updatedAt: new Date(),
            });
        }

        const amount = await this.getBalanceByYearMonth(data.companyId, data.year, data.month);
        balance.amount = amount;
        balance.updatedAt = new Date();
        await balance.save();
        return balance as ICashFlowBalance;
    }

    public async delete(data: DeleteMovementParams): Promise<void> {
        const movement = await CashFlow.movements.movementList.findOne({
            _id: data.id,
        });

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

        const companyId = movement.companyId;
        const movementDate = dayjs(movement.createdAt);
        const movementDateYear = movementDate.year();
        const movementDateMonth = movementDate.month();
        await CashFlow.movements.delete(data.id);
        await this.calculateBalance({
            companyId: companyId,
            year: movementDateYear,
            month: movementDateMonth,
            sessionUser: data.sessionUser,
        });
    }

    public async deleteByPayment(data: DeleteMovementByPaymentParams): Promise<void> {
        const payment = await Payments.payments.paymentsList.findOne({
            _id: data.paymentId,
        });

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

        const companyId = payment.companyId;
        const paymentDate = dayjs(payment.createdAt);
        const paymentYear = paymentDate.year();
        const paymentMonth = paymentDate.month();
        await CashFlow.deleteByPayment({
            companyId: companyId,
            paymentId: data.paymentId,
            sessionUser: data.sessionUser,
        });
        await this.calculateBalance({
            companyId: companyId,
            year: paymentYear,
            month: paymentMonth,
            sessionUser: data.sessionUser,
        });
    }

    public async deleteByClientAccountMovement(
        data: DeleteByClientAccountMovementParams
    ): Promise<void> {
        const clientAccountMovement = await ClientAccountMovements.movements.movementList.findOne({
            _id: data.clientAccountMovementId,
        });

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

        const companyId = clientAccountMovement.clientId;
        const movementDate = dayjs(clientAccountMovement.createdAt);
        const movementDateYear = movementDate.year();
        const movementDateMonth = movementDate.month();
        await ClientAccountMovements.movements.delete(data.clientAccountMovementId);
        await this.calculateBalance({
            companyId: companyId,
            year: movementDateYear,
            month: movementDateMonth,
            sessionUser: data.sessionUser,
        });
    }

    public async history(data: FindCashFlowBalanceParams): Promise<CashFlowHistoryView> {
        const returnData: CashFlowHistoryView = {
            companyId: data.companyId,
            data: [],
            total: 0,
        };
        const userSession = await this.validateSessionUser(data.sessionUser);
        await this.validatePermissions(String(userSession.id), data.companyId);

        const months = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
        const year = data.year;
        const companyId = data.companyId;

        let totalBalance = 0;

        for (const month of months) {
            //obtenemos los balances previamente calculados para cada mes/anio de una organizacion.
            const balance = await this.balance.findOne({
                year: year,
                month: month,
                companyId: companyId,
            });

            returnData.data.push({
                amount: balance ? balance.amount : 0,
                month: month,
                year: year,
            });

            totalBalance += balance ? balance.amount : 0;
        }

        return { ...returnData, ...{ total: totalBalance } };
    }
}

export default new CashFlowBalanceManager();
