import { createClient as createCustomClient } from '@supabase/supabase-js';

import axios from 'axios';
import { format } from 'date-fns';
import { config } from 'dotenv';
import { XMLParser } from 'fast-xml-parser';

function getLatestMessage(messages) {
  if (!messages?.length) {
    return null;
  }

  return messages.reduce((prev, current) =>
    Number(prev.messageId) > Number(current.messageId) ? prev : current,
  );
}

export function toArray<T>(input?: T | T[] | null): T[] {
  if (!input) return [];
  return Array.isArray(input) ? input : [input];
}

async function syncData() {
  if (process.env.NODE_ENV === 'local') {
    config({ path: `.env.${process.env.NODE_ENV}` });
  }

  const baseUrl = process.env.MEDIPOST_URL;
  const user = process.env.MEDIPOST_USER;
  const password = process.env.MEDIPOST_PASSWORD;
  const sender = process.env.MEDIPOST_MESSAGE_SENDER;
  const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
  const supabaseServiceRoleKey =
    process.env.NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY;

  if (
    !baseUrl ||
    !supabaseUrl ||
    !supabaseServiceRoleKey ||
    !user ||
    !password ||
    !sender
  ) {
    throw new Error('Could not access all necessary environment variables');
  }

  const supabase = createCustomClient(supabaseUrl, supabaseServiceRoleKey, {
    auth: {
      persistSession: false,
      autoRefreshToken: false,
      detectSessionInUrl: false,
    },
  });

  try {
    // GET LATEST PUBLIC MESSAGE ID
    const { data: lastChecked } = await supabase
      .schema('audit')
      .from('sync_entries')
      .select('created_at')
      .eq('status', 'SUCCESS')
      .order('created_at')
      .limit(1);

    const lastCheckedDate = lastChecked?.length
      ? {
          LastChecked: format(lastChecked[0].created_at, 'yyyy-MM-dd HH:mm:ss'),
        }
      : {};

    const { data } = await axios.get(baseUrl, {
      params: {
        Action: 'GetPublicMessageList',
        User: user,
        Password: password,
        Sender: sender,
        ...lastCheckedDate,
        MessageType: 'Teenus',
      },
    });

    if (data.code && data.code !== 0) {
      throw new Error('Failed to get public message list');
    }

    if (!data.messages.length) {
      return supabase.schema('audit').from('sync_entries').insert({
        operation: 'ANALYSES_SYNC',
        comment: 'No new data received',
        status: 'SUCCESS',
        changed_by_role: 'service_role',
      });
    }

    const latestMessage = getLatestMessage(data?.messages);

    // GET PUBLIC MESSAGE WITH GIVEN ID

    const { data: publicMessageData } = await axios.get(baseUrl, {
      params: {
        Action: 'GetPublicMessage',
        User: user,
        Password: password,
        MessageId: latestMessage.messageId,
      },
      headers: {
        Accept: 'application/xml',
      },
    });

    const parser = new XMLParser({ ignoreAttributes: false });
    const parsed = parser.parse(publicMessageData);

    if (parsed.ANSWER?.CODE && parsed.ANSWER?.CODE !== 0) {
      throw new Error(
        `Failed to get public message (id: ${latestMessage.messageId})`,
      );
    }

    // SAVE PUBLIC MESSAGE DATA

    const providers = toArray(parsed?.Saadetis?.Teenused.Teostaja);
    const analysisGroups = providers.flatMap((provider) =>
      toArray(provider.UuringuGrupp),
    );

    if (!parsed || !analysisGroups.length) {
      return supabase.schema('audit').from('sync_entries').insert({
        operation: 'ANALYSES_SYNC',
        comment: 'No data received',
        status: 'FAIL',
        changed_by_role: 'service_role',
      });
    }

    const codes: any[] = [];
    for (const analysisGroup of analysisGroups) {
      // SAVE ANALYSIS GROUP
      const { data: insertedAnalysisGroup, error } = await supabase
        .from('analysis_groups')
        .upsert(
          {
            original_id: analysisGroup.UuringuGruppId,
            name: analysisGroup.UuringuGruppNimi,
            order: analysisGroup.UuringuGruppJarjekord,
          },
          { onConflict: 'original_id', ignoreDuplicates: false },
        )
        .select('id');

      if (error || !insertedAnalysisGroup[0]?.id) {
        throw new Error(
          `Failed to insert analysis group (id: ${analysisGroup.UuringuGruppId}), error: ${error?.message}`,
        );
      }
      const analysisGroupId = insertedAnalysisGroup[0].id;

      const analysisGroupCodes = toArray(analysisGroup.Kood);
      codes.push(
        ...analysisGroupCodes.map((kood) => ({
          hk_code: kood.HkKood,
          hk_code_multiplier: kood.HkKoodiKordaja,
          coefficient: kood.Koefitsient,
          price: kood.Hind,
          analysis_group_id: analysisGroupId,
          analysis_element_id: null,
          analysis_id: null,
        })),
      );

      const analysisGroupItems = toArray(analysisGroup.Uuring);

      for (const item of analysisGroupItems) {
        const analysisElement = item.UuringuElement;

        const { data: insertedAnalysisElement, error } = await supabase
          .from('analysis_elements')
          .upsert(
            {
              analysis_id_oid: analysisElement.UuringIdOID,
              analysis_id_original: analysisElement.UuringId,
              tehik_short_loinc: analysisElement.TLyhend,
              tehik_loinc_name: analysisElement.KNimetus,
              analysis_name_lab: analysisElement.UuringNimi,
              order: analysisElement.Jarjekord,
              parent_analysis_group_id: analysisGroupId,
              material_groups: toArray(item.MaterjalideGrupp),
            },
            { onConflict: 'analysis_id_original', ignoreDuplicates: false },
          )
          .select('id');

        if (error || !insertedAnalysisElement[0]?.id) {
          throw new Error(
            `Failed to insert analysis element (id: ${analysisElement.UuringId}), error: ${error?.message}`,
          );
        }

        const insertedAnalysisElementId = insertedAnalysisElement[0].id;

        if (analysisElement.Kood) {
          const analysisElementCodes = toArray(analysisElement.Kood);
          codes.push(
            ...analysisElementCodes.map((kood) => ({
              hk_code: kood.HkKood,
              hk_code_multiplier: kood.HkKoodiKordaja,
              coefficient: kood.Koefitsient,
              price: kood.Hind,
              analysis_group_id: null,
              analysis_element_id: insertedAnalysisElementId,
              analysis_id: null,
            })),
          );
        }

        const analyses = analysisElement.UuringuElement;
        if (analyses?.length) {
          for (const analysis of analyses) {
            const { data: insertedAnalysis, error } = await supabase
              .from('analyses')
              .upsert(
                {
                  analysis_id_oid: analysis.UuringIdOID,
                  analysis_id_original: analysis.UuringId,
                  tehik_short_loinc: analysis.TLyhend,
                  tehik_loinc_name: analysis.KNimetus,
                  analysis_name_lab: analysis.UuringNimi,
                  order: analysis.Jarjekord,
                  parent_analysis_element_id: insertedAnalysisElementId,
                },
                { onConflict: 'analysis_id_original', ignoreDuplicates: false },
              )
              .select('id');

            if (error || !insertedAnalysis[0]?.id) {
              throw new Error(
                `Failed to insert analysis (id: ${analysis.UuringId}) error: ${error?.message}`,
              );
            }

            const insertedAnalysisId = insertedAnalysis[0].id;
            if (analysis.Kood) {
              const analysisCodes = toArray(analysis.Kood);

              codes.push(
                ...analysisCodes.map((kood) => ({
                  hk_code: kood.HkKood,
                  hk_code_multiplier: kood.HkKoodiKordaja,
                  coefficient: kood.Koefitsient,
                  price: kood.Hind,
                  analysis_group_id: null,
                  analysis_element_id: null,
                  analysis_id: insertedAnalysisId,
                })),
              );
            }
          }
        }
      }
    }

    await supabase.from('codes').upsert(codes);

    await supabase.schema('audit').from('sync_entries').insert({
      operation: 'ANALYSES_SYNC',
      status: 'SUCCESS',
      changed_by_role: 'service_role',
    });
  } catch (e) {
    await supabase
      .schema('audit')
      .from('sync_entries')
      .insert({
        operation: 'ANALYSES_SYNC',
        status: 'FAIL',
        comment: JSON.stringify(e),
        changed_by_role: 'service_role',
      });
    throw new Error(
      `Failed to sync public message data, error: ${JSON.stringify(e)}`,
    );
  }
}

syncData();
