'use server';

import { AccountWithParams } from '@/packages/features/accounts/src/types/accounts';
import { createNotificationsApi } from '@/packages/features/notifications/src/server/api';
import { getSupabaseServerAdminClient } from '@/packages/supabase/src/clients/server-admin-client';
import { listProductTypes } from '@lib/data';
import { initiateMultiPaymentSession, placeOrder } from '@lib/data/cart';
import type { StoreCart, StoreOrder } from '@medusajs/types';
import jwt from 'jsonwebtoken';
import { z } from 'zod';

import { getLogger } from '@kit/shared/logger';

import { createI18nServerInstance } from '~/lib/i18n/i18n.server';
import {
  bookAppointment,
  getConfirmedService,
} from '~/lib/services/connected-online.service';
import { sendOrderToMedipost } from '~/lib/services/medipost/medipostPrivateMessage.service';
import { handleNavigateToPayment } from '~/lib/services/medusaCart.service';
import { getOrderedAnalysisIds } from '~/lib/services/medusaOrder.service';
import {
  createAnalysisOrder,
  getAnalysisOrder,
} from '~/lib/services/order.service';
import { getOrderedTtoServices } from '~/lib/services/reservation.service';
import { FailureReason } from '~/lib/types/connected-online';

import { loadCurrentUserAccount } from './load-user-account';

const ANALYSIS_PACKAGES_TYPE_HANDLE = 'analysis-packages';
const ANALYSIS_TYPE_HANDLE = 'synlab-analysis';

const env = () =>
  z
    .object({
      emailSender: z
        .string({
          error: 'EMAIL_SENDER is required',
        })
        .min(1),
      siteUrl: z
        .string({
          error: 'NEXT_PUBLIC_SITE_URL is required',
        })
        .min(1),
      isEnabledDispatchOnMontonioCallback: z.boolean({
        error: 'MEDIPOST_ENABLE_DISPATCH_ON_MONTONIO_CALLBACK is required',
      }),
      medusaBackendPublicUrl: z
        .string({
          error: 'MEDUSA_BACKEND_PUBLIC_URL is required',
        })
        .min(1),
      companyBenefitsPaymentSecretKey: z
        .string({
          error: 'COMPANY_BENEFITS_PAYMENT_SECRET_KEY is required',
        })
        .min(1),
    })
    .parse({
      emailSender: process.env.EMAIL_SENDER,
      siteUrl: process.env.NEXT_PUBLIC_SITE_URL!,
      isEnabledDispatchOnMontonioCallback:
        process.env.MEDIPOST_ENABLE_DISPATCH_ON_MONTONIO_CALLBACK === 'true',
      medusaBackendPublicUrl: process.env.MEDUSA_BACKEND_PUBLIC_URL!,
      companyBenefitsPaymentSecretKey:
        process.env.COMPANY_BENEFITS_PAYMENT_SECRET_KEY!,
    });

export const initiatePayment = async ({
  accountId,
  benefitsAmount,
  cart,
  language,
}: {
  accountId: string;
  benefitsAmount: number;
  cart: StoreCart;
  language: string;
}) => {
  try {
    const {
      montonioPaymentSessionId,
      companyBenefitsPaymentSessionId,
      totalByMontonio,
      totalByBenefits,
      isFullyPaidByBenefits,
    } = await initiateMultiPaymentSession(cart, benefitsAmount);

    if (!isFullyPaidByBenefits) {
      if (!montonioPaymentSessionId) {
        throw new Error('Montonio payment session ID is missing');
      }
      const props = await handleNavigateToPayment({
        language,
        paymentSessionId: montonioPaymentSessionId,
        amount: totalByMontonio,
        currencyCode: cart.currency_code,
        cartId: cart.id,
      });
      return { ...props, isFullyPaidByBenefits };
    } else {
      // place order if all paid already
      const { orderId } = await handlePlaceOrder({ cart });

      const companyBenefitsOrderToken = jwt.sign(
        {
          accountId,
          companyBenefitsPaymentSessionId,
          orderId,
          totalByBenefits,
        },
        env().companyBenefitsPaymentSecretKey,
        {
          algorithm: 'HS256',
        },
      );
      const webhookResponse = await fetch(
        `${env().medusaBackendPublicUrl}/hooks/payment/company-benefits_company-benefits`,
        {
          method: 'POST',
          body: JSON.stringify({
            orderToken: companyBenefitsOrderToken,
          }),
          headers: {
            'Content-Type': 'application/json',
          },
        },
      );
      if (!webhookResponse.ok) {
        throw new Error('Failed to send company benefits webhook');
      }
      return { isFullyPaidByBenefits, orderId, unavailableLineItemIds: [] };
    }
  } catch (error) {
    console.error('Error initiating payment', error);
  }

  return {
    url: null,
    isFullyPaidByBenefits: false,
    orderId: null,
    unavailableLineItemIds: [],
  };
};

export async function handlePlaceOrder({ cart }: { cart: StoreCart }) {
  const logger = await getLogger();
  const { account } = await loadCurrentUserAccount();
  if (!account) {
    throw new Error('Account not found in context');
  }

  try {
    const medusaOrder = await placeOrder(cart.id, {
      revalidateCacheTags: false,
    });
    const orderedAnalysisElements = await getOrderedAnalysisIds({
      medusaOrder,
    });

    const orderContainsSynlabItems = !!orderedAnalysisElements?.length;

    try {
      const existingAnalysisOrder = await getAnalysisOrder({
        medusaOrderId: medusaOrder.id,
      });
      logger.info(
        `Analysis order already exists for medusaOrderId=${medusaOrder.id}, orderId=${existingAnalysisOrder.id}`,
      );
      return { success: true, orderId: existingAnalysisOrder.id };
    } catch {
      // ignored
    }

    let orderId: number | undefined = undefined;
    if (orderContainsSynlabItems) {
      orderId = await createAnalysisOrder({
        medusaOrder,
        orderedAnalysisElements,
      });
    }

    const orderResult = await getOrderResultParameters(medusaOrder);

    const { medusaOrderId, email, analysisPackageOrder, analysisItemsOrder } =
      orderResult;

    const orderedTtoServices = await getOrderedTtoServices({ medusaOrder });
    let bookServiceResults: {
      success: boolean;
      reason?: FailureReason;
      bookingCode: string | null;
      clinicKey: string | null;
    }[] = [];
    if (orderedTtoServices?.length) {
      const bookingPromises = orderedTtoServices.map((service) =>
        bookAppointment(
          service.service_id,
          service.clinic_id,
          service.service_user_id,
          service.sync_user_id,
          service.start_time,
        ),
      );
      bookServiceResults = await Promise.all(bookingPromises);
    }

    if (email) {
      if (analysisPackageOrder) {
        await sendAnalysisPackageOrderEmail({
          account,
          email,
          analysisPackageOrder,
        });
      } else {
        logger.info(`Order has no analysis package, skipping email.`);
      }

      if (analysisItemsOrder) {
        // @TODO send email for separate analyses
        logger.warn(
          `Order has analysis items, but no email template exists yet`,
        );
      } else {
        logger.info(`Order has no analysis items, skipping email.`);
      }
    } else {
      logger.error('Missing email to send order result email', orderResult);
    }

    if (bookServiceResults?.length) {
      bookServiceResults.forEach(async ({ bookingCode, clinicKey }) => {
        if (!bookingCode || !clinicKey) {
          logger.info('A booking is missing either bookingCode or clinicKey', {
            bookingCode,
            clinicKey,
          });
          return;
        }
        await getConfirmedService(bookingCode, clinicKey);
      });
    }

    if (env().isEnabledDispatchOnMontonioCallback && orderContainsSynlabItems) {
      await sendOrderToMedipost({ medusaOrderId, orderedAnalysisElements });
    }

    if (bookServiceResults.some(({ success }) => success === false)) {
      const failedServiceBookings = bookServiceResults.filter(
        ({ success }) => success === false,
      );
      return {
        success: false,
        failedServiceBookings,
        orderId,
      };
    }

    return { success: true, orderId };
  } catch (error) {
    console.error('Failed to place order', error);
    throw new Error(`Failed to place order, message=${error}`);
  }
}

async function sendAnalysisPackageOrderEmail({
  account,
  email,
  analysisPackageOrder,
}: {
  account: AccountWithParams;
  email: string;
  analysisPackageOrder: {
    partnerLocationName: string;
    analysisPackageName: string;
  };
}) {
  const { language } = await createI18nServerInstance();
  const { analysisPackageName, partnerLocationName } = analysisPackageOrder;
  try {
    await sendEmail({
      account: { id: account.id, name: account.name },
      email,
      analysisPackageName,
      partnerLocationName,
      language,
    });
    console.info(`Successfully sent analysis package order email to ${email}`);
  } catch (error) {
    console.error(
      `Failed to send analysis package order email to ${email}`,
      error,
    );
  }
}

async function getOrderResultParameters(medusaOrder: StoreOrder) {
  const { productTypes } = await listProductTypes();
  const analysisPackagesType = productTypes.find(
    ({ metadata }) => metadata?.handle === ANALYSIS_PACKAGES_TYPE_HANDLE,
  );
  const analysisType = productTypes.find(
    ({ metadata }) => metadata?.handle === ANALYSIS_TYPE_HANDLE,
  );

  const analysisPackageOrderItem = medusaOrder.items?.find(
    ({ product_type_id }) => product_type_id === analysisPackagesType?.id,
  );
  const analysisItems = medusaOrder.items?.filter(
    ({ product_type_id }) => product_type_id === analysisType?.id,
  );

  return {
    medusaOrderId: medusaOrder.id,
    email: medusaOrder.email,
    analysisPackageOrder: analysisPackageOrderItem
      ? {
          partnerLocationName:
            (analysisPackageOrderItem?.metadata
              ?.partner_location_name as string) ?? '',
          analysisPackageName: analysisPackageOrderItem?.title ?? '',
        }
      : null,
    analysisItemsOrder:
      Array.isArray(analysisItems) && analysisItems.length > 0
        ? analysisItems.map(({ product }) => ({
            analysisName: product?.title ?? '',
            analysisId: (product?.metadata?.analysisIdOriginal as string) ?? '',
          }))
        : null,
  };
}

const sendEmail = async ({
  account,
  email,
  analysisPackageName,
  partnerLocationName,
  language,
}: {
  account: Pick<AccountWithParams, 'name' | 'id'>;
  email: string;
  analysisPackageName: string;
  partnerLocationName: string;
  language: string;
}) => {
  const client = getSupabaseServerAdminClient();
  try {
    const { renderSynlabAnalysisPackageEmail } = await import(
      '@kit/email-templates'
    );
    const { getMailer } = await import('@kit/mailers');

    const mailer = await getMailer();

    const { html, subject } = await renderSynlabAnalysisPackageEmail({
      analysisPackageName,
      personName: account.name,
      partnerLocationName,
      language,
    });

    await mailer
      .sendEmail({
        from: env().emailSender,
        to: email,
        subject,
        html,
      })
      .catch((error) => {
        throw new Error(`Failed to send email, message=${error}`);
      });
    await createNotificationsApi(client).createNotification({
      account_id: account.id,
      body: html,
    });
  } catch (error) {
    throw new Error(`Failed to send email, message=${error}`);
  }
};
