import {
  renderAllResultsReceivedEmail,
  renderFirstResultsReceivedEmail,
  renderOrderProcessingEmail,
  renderPatientFirstResultsReceivedEmail,
  renderPatientFullResultsReceivedEmail,
  renderTtoReservationConfirmationEmail,
} from '@kit/email-templates';
import { getLogger } from '@kit/shared/logger';
import { getFullName } from '@kit/shared/utils';
import { Database } from '@kit/supabase/database';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';

import {
  getAssignedDoctorAccount,
  getDoctorAccounts,
  getUserContactAdmin,
} from '~/lib/services/account.service';
import {
  NotificationAction,
  createNotificationLog,
} from '~/lib/services/audit/notificationEntries.service';
import {
  EmailRenderer,
  sendEmailFromTemplate,
} from '~/lib/services/mailer.service';

type AnalysisOrder = Database['medreport']['Tables']['analysis_orders']['Row'];
type TtoReservation =
  Database['medreport']['Tables']['connected_online_reservation']['Row'];

export function createOrderWebhooksService() {
  return new OrderWebhooksService();
}

class OrderWebhooksService {
  private readonly namespace = 'orders.webhooks';

  async handleStatusChangeWebhook(analysisOrder: AnalysisOrder) {
    const logger = await getLogger();

    const ctx = {
      analysisOrderId: analysisOrder.id,
      namespace: this.namespace,
    };

    logger.info(ctx, 'Received status change update. Processing...');
    let actions: NotificationAction[] = [];
    try {
      if (analysisOrder.status === 'PROCESSING') {
        actions = [NotificationAction.PATIENT_ORDER_PROCESSING];
        await this.sendProcessingNotification(analysisOrder);
      }

      if (analysisOrder.status === 'PARTIAL_ANALYSIS_RESPONSE') {
        actions = [
          NotificationAction.PATIENT_FIRST_RESULTS_RECEIVED,
          NotificationAction.DOCTOR_NEW_JOBS,
        ];

        await this.sendPartialAnalysisResultsNotifications(analysisOrder);
      }

      if (analysisOrder.status === 'FULL_ANALYSIS_RESPONSE') {
        actions = [
          NotificationAction.DOCTOR_PATIENT_RESULTS_RECEIVED,
          NotificationAction.PATIENT_FULL_RESULTS_RECEIVED,
        ];
        await this.sendFullAnalysisResultsNotifications(analysisOrder);
      }

      if (actions.length) {
        return logger.info(ctx, 'Status change notifications sent.');
      }

      logger.info(ctx, 'Status change processed. No notifications to send.');
    } catch (e: any) {
      if (actions.length)
        await Promise.all(
          actions.map((action) =>
            createNotificationLog({
              action,
              status: 'FAIL',
              comment: e?.message,
              relatedRecordId: analysisOrder.id,
            }),
          ),
        );
      logger.error(
        ctx,
        `Error while processing status change: ${JSON.stringify(e)}`,
      );
    }
  }

  async handleTtoReservationConfirmationWebhook(
    ttoReservation: TtoReservation,
  ) {
    const logger = await getLogger();
    const ctx = {
      ttoReservationId: ttoReservation.id,
      namespace: this.namespace,
    };

    try {
      const userContact = await getUserContactAdmin(ttoReservation.user_id);
      if (!userContact.email) {
        await createNotificationLog({
          action: NotificationAction.TTO_ORDER_CONFIRMATION,
          status: 'FAIL',
          comment: 'No email found for ' + ttoReservation.user_id,
          relatedRecordId: ttoReservation.id,
        });
        logger.warn(ctx, 'No email found ');
        return;
      }
      const supabaseClient = getSupabaseServerAdminClient();
      if (!ttoReservation.medusa_cart_line_item_id) {
        await createNotificationLog({
          action: NotificationAction.TTO_ORDER_CONFIRMATION,
          status: 'FAIL',
          comment: 'No cart item id found for ' + ttoReservation.user_id,
          relatedRecordId: ttoReservation.id,
        });
        logger.warn(ctx, 'No cart item id found ');
        return;
      }
      const [
        { data: cartItem },
        { data: location },
        { data: serviceProvider },
      ] = await Promise.all([
        supabaseClient
          .from('cart_line_item')
          .select('cart_id,title')
          .eq('id', ttoReservation.medusa_cart_line_item_id)
          .single(),
        supabaseClient
          .schema('medreport')
          .from('connected_online_locations')
          .select('name,address')
          .eq('sync_id', ttoReservation.location_sync_id || 0)
          .single(),
        supabaseClient
          .schema('medreport')
          .from('connected_online_providers')
          .select('email,phone_number,name')
          .eq('id', ttoReservation.clinic_id)
          .single(),
      ]);

      if (!cartItem) {
        await createNotificationLog({
          action: NotificationAction.TTO_ORDER_CONFIRMATION,
          status: 'FAIL',
          comment: 'No medusa cart item found for ' + ttoReservation.user_id,
          relatedRecordId: ttoReservation.medusa_cart_line_item_id,
        });
        logger.warn(ctx, 'No cart item found ');
        return;
      }

      const [{ data: orderCart }] = await Promise.all([
        supabaseClient
          .from('order_cart')
          .select('order_id')
          .eq('cart_id', cartItem.cart_id)
          .single(),
      ]);

      if (!orderCart) {
        await createNotificationLog({
          action: NotificationAction.TTO_ORDER_CONFIRMATION,
          status: 'FAIL',
          comment: 'No medusa order cart found for ' + ttoReservation.user_id,
          relatedRecordId: cartItem.cart_id,
        });
        logger.warn(ctx, 'No order cart found ');
        return;
      }

      await sendEmailFromTemplate(
        renderTtoReservationConfirmationEmail,
        {
          language: userContact.preferred_locale ?? 'et',
          recipientName: getFullName(userContact.name, userContact.last_name),
          startTime: ttoReservation.start_time,
          orderName: cartItem.title,
          locationName: location?.name,
          locationAddress: location?.address,
          orderId: orderCart.order_id,
          serviceProviderName: serviceProvider?.name,
          serviceProviderEmail: serviceProvider?.email,
          serviceProviderPhone: serviceProvider?.phone_number,
        },
        userContact.email,
      );

      return createNotificationLog({
        action: NotificationAction.TTO_ORDER_CONFIRMATION,
        status: 'SUCCESS',
        relatedRecordId: orderCart.order_id,
      });
    } catch (e: any) {
      createNotificationLog({
        action: NotificationAction.TTO_ORDER_CONFIRMATION,
        status: 'FAIL',
        comment: e?.message,
        relatedRecordId: ttoReservation.id,
      });
      logger.error(
        ctx,
        `Error while processing tto reservation: ${JSON.stringify(e)}`,
      );
    }
  }

  async sendProcessingNotification(analysisOrder: AnalysisOrder) {
    const logger = await getLogger();
    const supabase = getSupabaseServerAdminClient();

    const userContact = await getUserContactAdmin(analysisOrder.user_id);

    if (!userContact?.email) {
      await createNotificationLog({
        action: NotificationAction.PATIENT_ORDER_PROCESSING,
        status: 'FAIL',
        comment: 'No email found for ' + analysisOrder.user_id,
        relatedRecordId: analysisOrder.id,
      });
      logger.warn(
        { analysisOrderId: analysisOrder.id, namespace: this.namespace },
        'No email found ',
      );
      return;
    }

    const [{ data: medusaOrder }, { data: analysisElements }] =
      await Promise.all([
        supabase
          .from('order')
          .select('id,metadata')
          .eq('id', analysisOrder.medusa_order_id)
          .single()
          .throwOnError(),
        supabase
          .schema('medreport')
          .from('analysis_elements')
          .select('materialGroups:material_groups')
          .in('id', analysisOrder.analysis_element_ids ?? [])
          .throwOnError(),
      ]);

    let isUrine = false;
    for (const analysisElement of analysisElements ?? []) {
      logger.info({ group: analysisElement.materialGroups ?? [] });

      const containsUrineSample = (analysisElement.materialGroups ?? [])?.some(
        (element) =>
          (element as { Materjal?: { MaterjaliNimi: string } })?.Materjal
            ?.MaterjaliNimi === 'Uriin',
      );

      if (containsUrineSample) {
        isUrine = true;
        break;
      }
    }

    const orderMetadata = medusaOrder.metadata as {
      partner_location_name?: string;
    };

    await sendEmailFromTemplate(
      renderOrderProcessingEmail,
      {
        language: userContact.preferred_locale ?? 'et',
        recipientName: getFullName(userContact.name, userContact.last_name),
        partnerLocation: orderMetadata.partner_location_name ?? 'SYNLAB',
        isUrine,
      },
      userContact.email,
    );

    return createNotificationLog({
      action: NotificationAction.PATIENT_ORDER_PROCESSING,
      status: 'SUCCESS',
      relatedRecordId: analysisOrder.id,
    });
  }

  async sendPatientUpdateNotification(
    analysisOrder: AnalysisOrder,
    template: EmailRenderer,
    action: NotificationAction,
  ) {
    const logger = await getLogger();

    const userContact = await getUserContactAdmin(analysisOrder.user_id);

    if (userContact?.email) {
      await sendEmailFromTemplate(
        template,
        {
          analysisOrderId: analysisOrder.id,
          recipientName: getFullName(userContact.name, userContact.last_name),
          language: userContact.preferred_locale ?? 'et',
        },
        userContact.email,
      );
      await createNotificationLog({
        action,
        status: 'SUCCESS',
        relatedRecordId: analysisOrder.id,
      });
      logger.info(
        { analysisOrderId: analysisOrder.id, namespace: this.namespace },
        'Sent notification email',
      );
    } else {
      await createNotificationLog({
        action,
        status: 'FAIL',
        comment: 'No email found for ' + analysisOrder.user_id,
        relatedRecordId: analysisOrder.id,
      });
      logger.warn(
        { analysisOrderId: analysisOrder.id, namespace: this.namespace },
        'No email found ',
      );
    }
  }

  async sendPartialAnalysisResultsNotifications(analysisOrder: AnalysisOrder) {
    const logger = await getLogger();

    await this.sendPatientUpdateNotification(
      analysisOrder,
      renderPatientFirstResultsReceivedEmail,
      NotificationAction.PATIENT_FIRST_RESULTS_RECEIVED,
    );

    const doctorAccounts = await getDoctorAccounts();
    const doctorEmails: string[] = doctorAccounts
      .map(({ email }) => email)
      .filter((email): email is string => !!email);

    await sendEmailFromTemplate(
      renderFirstResultsReceivedEmail,
      {
        analysisOrderId: analysisOrder.id,
        language: 'et',
      },
      doctorEmails,
    );

    logger.info(
      { analysisOrderId: analysisOrder.id, namespace: this.namespace },
      'Sent out partial analysis results notifications for doctors',
    );

    await createNotificationLog({
      action: NotificationAction.DOCTOR_NEW_JOBS,
      status: 'SUCCESS',
      relatedRecordId: analysisOrder.id,
    });
  }

  async sendFullAnalysisResultsNotifications(analysisOrder: AnalysisOrder) {
    await this.sendPatientUpdateNotification(
      analysisOrder,
      renderPatientFullResultsReceivedEmail,
      NotificationAction.PATIENT_FULL_RESULTS_RECEIVED,
    );

    const doctorAccount = await getAssignedDoctorAccount(analysisOrder.id);
    const assignedDoctorEmail = doctorAccount?.email;

    if (!assignedDoctorEmail) {
      return;
    }

    await sendEmailFromTemplate(
      renderAllResultsReceivedEmail,
      {
        analysisOrderId: analysisOrder.id,
        language: 'et',
      },
      assignedDoctorEmail,
    );

    return createNotificationLog({
      action: NotificationAction.DOCTOR_PATIENT_RESULTS_RECEIVED,
      status: 'SUCCESS',
      relatedRecordId: analysisOrder.id,
    });
  }
}
