import { io } from "../../index";
import {
    IPlanSuscriptionDocument,
    MP_SUBS_STATUS,
    PlanSuscriptionsAdapterMongoose,
} from "./PlanSubscriptions.Adapter.Mongoose";
import UsersManager from "../Users/Users";
import {
    CancellPlanSuscriptionParams,
    CheckFeatureParams,
    CreateMPPreApprovalParams,
    DeletePlanSuscriptionByUserParams,
    FindPlanSuscripcionsParams,
    GetSubscriptionInitPointParams,
    GetSuscriptionInitPointResponse,
    IPlanSuscriptionsManager,
    ISubscriptionInfo,
    MP_PRE_APPROVAL_STATUS,
    MPPreApprovalResponse,
    ToFreePlanParams,
    ToMpParams,
} from "./PlanSubscriptions.interface";
import axios from "axios";
import PlansList from "../../Models/Plans/Plans";
import dayjs from "dayjs";
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
import "dayjs/locale/es";
import { isNull } from "../../helpers/IsNull";
import { PlanFeatures } from "../Plans/Plans.interface";
import { NotificationsManager } from "../Notifications/Notifications";
import WapServerList from "../WapServer/WapServer";
import CompaniesList from "../Companies/Companies";
import { NotificationType } from "../../Models/SystemNotifications/SystemNotification.Interface";

dayjs.locale("es");
dayjs.extend(isSameOrAfter);

class PlanSuscriptionsManager implements IPlanSuscriptionsManager {
    planSuscriptions: PlanSuscriptionsAdapterMongoose;

    constructor() {
        this.planSuscriptions = new PlanSuscriptionsAdapterMongoose();
    }

    public async getInitPoint(
        data: GetSubscriptionInitPointParams
    ): Promise<GetSuscriptionInitPointResponse> {
        const sessionUser = await UsersManager.users.findOne({
            _id: data.sessionUser,
        });

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

        const subscription = await this.planSuscriptions.findOne({
            sessionUser: data.sessionUser,
        });

        if (!subscription) {
            throw new Error("No se ha encontrado una suscripción activa");
        }

        const plan = await PlansList.plans.findOne({
            _id: String(subscription.planId),
        });

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

        if (plan.price == 0) {
            throw new Error("El plan gratuito no requiere el pago de una suscripción");
        }

        if (!subscription.mpPreferenceId) {
            throw new Error("No se ha encontrado una suscripción activa");
        }

        if (subscription.mpStatus !== MP_SUBS_STATUS.PENDING) {
            throw new Error("No se ha encontrado una suscripción pendiente de pago");
        }

        if (!subscription.mpInitPoint) {
            throw new Error("No se ha encontrado una suscripción pendiente de pago");
        }

        return {
            init_point: subscription.mpInitPoint,
        };
    }

    public async deletePlanSuscriptionByUser(
        data: DeletePlanSuscriptionByUserParams
    ): Promise<void> {
        await this.planSuscriptions.delete({ sessionUser: data.sessionUser });
    }

    public async create(data: CreateMPPreApprovalParams): Promise<MPPreApprovalResponse> {
        const sessionUser = await UsersManager.users.findOne({
            _id: data.sessionUser,
        });

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

        const plan = await PlansList.plans.findOne({ _id: data.planId });

        if (!plan) {
            throw new Error("El plan no existe");
        }

        if (!plan.active) {
            throw new Error("El plan no esta activo");
        }

        let subscription = await this.planSuscriptions.findOne({
            sessionUser: data.sessionUser,
        });

        let isNewSubscription = false;

        if (!subscription) {
            const subscriptionStartDate = dayjs(new Date());

            if (plan.price === 0) {
                isNewSubscription = true;
                subscription = await this.planSuscriptions.create({
                    userId: data.sessionUser,
                    planId: data.planId,
                    mpPlanId: data.planId,
                    startDate: subscriptionStartDate.toDate(),
                    endDate: subscriptionStartDate.clone().add(data.months || 1, "months").toDate(),
                    autoRenew: true,
                    mpInitPoint: "/landing/dashboard",
                });
            } else if (plan.price > 0) {


                isNewSubscription = true;
                subscription = await this.toMP({
                    planId: data.planId,
                    sessionUser: data.sessionUser,
                    months: data.months || 1,
                });
            }
        } else {
            const actualPlan = await PlansList.plans.findOne({
                _id: subscription.planId,
            });
            if (!actualPlan) {
                throw new Error("El plan no existe");
            }
            isNewSubscription = true;

            const dashboardUrl = "/landing/dashboard";

            if (plan.price === 0) {
                if (actualPlan.id !== data.planId) {
                    await this.toFreePlan({ sessionUser: data.sessionUser });
                    subscription = await this.planSuscriptions.findOne({ sessionUser: data.sessionUser });
                }
                
                if (subscription) {
                    const subscriptionStartDate = dayjs(new Date());
                    subscription.endDate = subscriptionStartDate.clone().add(data.months || 1, "months").toDate();
                    subscription.mpInitPoint = dashboardUrl || "";
                    await subscription.save();
                }
            } else {
                // Actualizamos el plan si actualmente tiene el plan gratuito.
                if (actualPlan.id !== data.planId && actualPlan.price === 0) {
                    //realizar la suscripcion...
                    subscription = await this.toMP({
                        planId: data.planId,
                        sessionUser: data.sessionUser,
                        months: data.months || 1,
                    });
                } else {
                    if (subscription.mpStatus != MP_SUBS_STATUS.CANCELLED && actualPlan.id !== data.planId) {
                        throw new Error(
                            "No se puede cambiar el plan, ya que el usuario ya tiene una suscripción activa. Para cambiarlo primero debes cancelar tu plan actual."
                        );
                    }

                    subscription = await this.toMP({
                        planId: data.planId,
                        sessionUser: data.sessionUser,
                        months: data.months || 1,
                    });
                }
            }
        }

        if (!subscription) {
            throw new Error("No fue posible crear la suscripción");
        }

        return {
            init_point: isNewSubscription
                ? subscription.mpInitPoint
                : isNull<string>(process.env.MP_BACK_URL, ""),
            status: MP_PRE_APPROVAL_STATUS.READY_TO_START,
        };
    }

    public async toMP(data: ToMpParams): Promise<IPlanSuscriptionDocument> {
        const sessionUser = await UsersManager.users.findOne({
            _id: data.sessionUser,
        });

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

        const plan = await PlansList.plans.findOne({ _id: data.planId });

        if (!plan) {
            throw new Error("El plan no existe");
        }

        try {
            const subscriptionStartDate = dayjs(new Date());

            let discount = 0;
            if (data.months === 3) discount = plan.discount3Months || 0;
            else if (data.months === 6) discount = plan.discount6Months || 0;
            else if (data.months === 12) discount = plan.discount12Months || 0;
            
            const totalPrice = plan.price * data.months;
            const finalPrice = totalPrice - (totalPrice * (discount / 100));

            const response = await axios.post(
                "https://api.mercadopago.com/checkout/preferences",
                {
                    items: [
                        {
                            title: "TurnosXpress.com.ar - Plan " + plan.name + " (" + data.months + " meses)",
                            quantity: 1,
                            currency_id: "ARS",
                            unit_price: finalPrice
                        }
                    ],
                    back_urls: {
                        success: process.env.MP_BACK_URL,
                        failure: process.env.MP_BACK_URL,
                        pending: process.env.MP_BACK_URL
                    },
                    auto_return: "approved",
                    payer: {
                        email: sessionUser.mpPayerEmail || ""
                    },
                    external_reference: String(sessionUser._id),
                    notification_url: process.env.MP_WEBHOOK_URL
                },
                {
                    headers: {
                        Authorization: `Bearer ${process.env.MP_ACCESS_TOKEN}`,
                        "Content-Type": "application/json",
                    },
                }
            );

            const subscriptionData = response.data;

            this.planSuscriptions.delete({ sessionUser: data.sessionUser });

            const subscription = await this.planSuscriptions.create({
                userId: data.sessionUser,
                planId: data.planId,
                mpPlanId: data.planId,
                startDate: subscriptionStartDate.toDate(),
                endDate: subscriptionStartDate.clone().add(data.months, "months").toDate(),
                autoRenew: false,
                mpPayerEmail: sessionUser.mpPayerEmail,
                mpStatus: MP_SUBS_STATUS.PENDING,
                mpDateCreated: new Date(),
                mpInitPoint: subscriptionData.init_point,
                mpPreferenceId: subscriptionData.id,
            });

            await NotificationsManager.sendSystemNotification({
                userId: String(data.sessionUser),
                subject: "Se ha creado una nueva suscripción",
                message: `Se ha creado una nueva suscripción para el plan ${plan.name}.`,
                type: NotificationType.BILLING,
                code: String(subscription._id)
            });

            return subscription;
        } catch (error) {
            console.log("ha ocurrido un error:", error);
            throw new Error("No fue posible crear la suscripción en el proveedor de pagos");
        }
    }

    public async cancell(data: CancellPlanSuscriptionParams): Promise<void> {
        const sessionUser = await UsersManager.users.findOne({
            _id: data.sessionUser,
        });

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

        const subscription = await this.planSuscriptions.findOne({
            sessionUser: data.sessionUser,
        });

        if (!subscription) {
            throw new Error("No se ha encontrado una suscripción activa");
        }

        if (subscription.mpStatus === MP_SUBS_STATUS.CANCELLED) {
            throw new Error("La suscripción ya se encuentraba cancelada");
        }

        try {
            if (subscription.mpPreferenceId) {
                // Preferences don't need to be explicitly cancelled in MercadoPago
                // We just cancel it locally if not paid, or change to free plan if paid.
            }

            const dateNow = dayjs(new Date()).endOf("day");
            const dateEnd = dayjs(subscription.endDate);

            if (dateNow.isSameOrAfter(dateEnd)) {
                this.toFreePlan({
                    sessionUser: data.sessionUser,
                });
            } else {
                subscription.mpStatus = MP_SUBS_STATUS.CANCELLED;
                subscription.mpInitPoint = "";
                subscription.mpPayerEmail = "";
                subscription.mpPayerId = -1;
                await subscription.save();
            }
        } catch (error) {
            console.log("ha ocurrido un error:", error);
            throw new Error("No fue posible crear la suscripción en el proveedor de pagos");
        }
    }

    public async toFreePlan(data: ToFreePlanParams): Promise<void> {
        console.log("intentando aplicar el free plan...");

        const sessionUser = await UsersManager.users.findOne({
            _id: data.sessionUser,
        });

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

        const subscription = await this.planSuscriptions.findOne({
            sessionUser: data.sessionUser,
        });

        if (!subscription) {
            throw new Error("No se ha encontrado una suscripción activa");
        }

        const freePlan = await PlansList.plans.findOne({ price: 0 });
        if (!freePlan || !freePlan._id) {
            throw new Error("No se ha encontrado el plan gratuito");
        }

        if (String(subscription.planId) == String(freePlan._id)) {
            //console.log("El usuario ya tiene el plan gratuito");
            return;
        }

        const company = await CompaniesList.companies.findOne({
            ownerId: data.sessionUser,
        });

        if (company) {
            //Intentamos eliminar el bot de el usuario.
            try {
                //console.log("intentando borrar el bot.");
                await WapServerList.deleteBot({
                    companyId: String(company._id),
                    sessionUser: data.sessionUser,
                });
            } catch (error) {
                console.log("ha ocurrido un error al intentar eliminar el bot:", error);
            }
        }

        //console.log("guardando el plan gratuito...");
        //try {
        console.log("cambiando al free plan:", freePlan.id);
        subscription.planId = freePlan.id;
        subscription.mpStatus = "";
        subscription.isActive = true;
        subscription.mpInitPoint = "";
        subscription.mpPreferenceId = "";

        await subscription.save();
        //  console.log("plan gratuito guardado:", freePlan.id);
        //} catch (error) {
        //  console.log("no se pudo guardar el plan gratuito.");
        //}

        await NotificationsManager.sendSystemNotification({
                userId: String(data.sessionUser),
                subject: "Bienvenido al plan gratuito",
                message: `El plan gratuito ha sido asignado a su usuario. Cuando lo desee puede actualizar su plan para acceder más funciones.`,
                type: NotificationType.BILLING,
                code: String(subscription._id)
            });

        const susInfo: ISubscriptionInfo = {
            id: subscription.id,
            plan: freePlan,
            startDate: subscription.startDate,
            endDate: subscription.endDate,
            isActive: subscription.isActive,
            autoRenew: subscription.autoRenew,
            mpStatus: subscription.mpStatus,
            mpDateCreated: subscription.mpDateCreated,
        };

        io.to(`user:${String(subscription.userId)}`).emit("suscription_status_updated", susInfo);
    }

    public async findOne(
        data: FindPlanSuscripcionsParams
    ): Promise<IPlanSuscriptionDocument | null> {
        const subscription = await this.planSuscriptions.findOne(data);

        //console.log("findOne", subscription);

        if (!subscription) {
            return null;
        }

        const dateNow = dayjs(new Date()).endOf("day");
        const dateEnd = dayjs(subscription.endDate);

        if (dateNow.isSameOrAfter(dateEnd)) {
            if (subscription.mpStatus === MP_SUBS_STATUS.CANCELLED) {
                this.toFreePlan({
                    sessionUser: data.sessionUser,
                });
            }
        }

        return await this.planSuscriptions.findOne(data);
    }

    public async getSubscriptionByUser(
        data: FindPlanSuscripcionsParams
    ): Promise<ISubscriptionInfo | null> {
        const subscription = await this.planSuscriptions.findOne(data);

        if (!subscription) {
            return null;
        }

        const dateNow = dayjs(new Date()).endOf("day");
        const dateEnd = dayjs(subscription.endDate);

        if (dateNow.isSameOrAfter(dateEnd)) {
            //Esta condicion era la culpable de que todo el tiempo se queria aplicar el free plan.
            //Ya que la fecha de finalizacion queda guardada en la base de datos a pesar de que ya no se use.
            if (
                subscription.mpStatus !== MP_SUBS_STATUS.AUTHORIZED &&
                subscription.mpStatus !== ""
            ) {
                this.toFreePlan({
                    sessionUser: data.sessionUser,
                });
            }
        }

        const plan = await PlansList.plans.findOne({
            _id: subscription.planId,
        });

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

        return {
            id: subscription.id,
            plan: plan,
            startDate: subscription.startDate,
            endDate: subscription.endDate,
            isActive: subscription.isActive,
            autoRenew: subscription.autoRenew,
            mpStatus: subscription.mpStatus,
            mpDateCreated: subscription.mpDateCreated,
        };
    }

    public async checkFeature(data: CheckFeatureParams): Promise<boolean> {
        const subscriptionInfo = await this.getSubscriptionByUser({
            sessionUser: data.userId,
        });
        if (!subscriptionInfo) {
            throw new Error("No se encontro una suscripcion activa");
        }
        const plan = subscriptionInfo.plan;

        if (!plan) {
            throw new Error("No se encontro el plan de la suscripcion");
        }

        switch (data.feature) {
            case PlanFeatures.WAP_NOTIFICATIONS:
                if (plan.wapNotifications) {
                    return true;
                }
                break;
            case PlanFeatures.MAIL_NOTIFICATIONS:
                if (plan.mailNotifications) {
                    return true;
                }
                break;
            case PlanFeatures.SMS_NOTIFICATIONS:
                if (plan.smsNotifications) {
                    return true;
                }
                break;
            case PlanFeatures.HAS_BOT:
                if (plan.bot) {
                    return true;
                }
                break;
            case PlanFeatures.PAYMENTS:
                if (plan.payments) {
                    return true;
                }
                break;
        }
        return false;
    }

    public async isSubscriptionActive(data: FindPlanSuscripcionsParams): Promise<boolean> {
        const subscriptionInfo = await this.getSubscriptionByUser(data);
        if (!subscriptionInfo) {
            throw new Error("No se encontro una suscripcion activa");
        }
        return subscriptionInfo.isActive;
    }
}

const PlanSubscriptionsList = new PlanSuscriptionsManager();

export default PlanSubscriptionsList;
