import axios from 'axios';
import { XMLParser } from 'fast-xml-parser';
import fs from 'fs';

import { toArray } from '@kit/shared/utils';

import {
  createAnalysis,
  createNoDataReceivedEntry,
  createNoNewDataReceivedEntry,
  createSyncFailEntry,
  createSyncSuccessEntry,
  getAnalyses,
} from '~/lib/services/analyses.service';
import {
  AnalysisElement,
  createAnalysisElement,
  getAnalysisElements,
} from '~/lib/services/analysis-element.service';
import {
  createAnalysisGroup,
  getAnalysisGroups,
} from '~/lib/services/analysis-group.service';
import { createCodes } from '~/lib/services/codes.service';
import { IMedipostPublicMessageDataParsed } from '~/lib/services/medipost/medipost.types';
import { getLatestPublicMessageListItem } from '~/lib/services/medipost/medipostPublicMessage.service';
import { getLastCheckedDate } from '~/lib/services/sync-entries.service';
import type { ICode } from '~/lib/types/code';

const WRITE_XML_TO_FILE = false as boolean;

export default async function syncAnalysisGroups() {
  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;

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

  try {
    console.info('Getting latest public message id');
    const lastCheckedDate = await getLastCheckedDate();

    const latestMessage = await getLatestPublicMessageListItem();
    if (!latestMessage) {
      console.info('No new data received');
      await createNoNewDataReceivedEntry();
      return;
    }
    console.info('Getting public message with id: ', latestMessage.messageId);

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

    if (WRITE_XML_TO_FILE) {
      fs.writeFileSync('public-messages-list-response.xml', publicMessageData);
    }

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

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

    const existingAnalysisGroups = await getAnalysisGroups();

    // SAVE PUBLIC MESSAGE DATA

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

    if (!parsed || !analysisGroups.length) {
      console.info('No analysis groups data received');
      await createNoDataReceivedEntry();
      return;
    }

    const codes: ICode[] = [];
    for (const analysisGroup of analysisGroups) {
      const existingAnalysisGroup = existingAnalysisGroups?.find(
        ({ original_id }) => original_id === analysisGroup.UuringuGruppId,
      );
      let groupExistingAnalysisElements: AnalysisElement[] = [];
      let analysisGroupId: number;
      if (existingAnalysisGroup) {
        console.info(
          `Analysis group '${analysisGroup.UuringuGruppNimi}' already exists, only creating new analysis elements`,
        );
        groupExistingAnalysisElements = await getAnalysisElements({
          analysisGroupId: existingAnalysisGroup.id,
        });
        analysisGroupId = existingAnalysisGroup.id;
      } else {
        analysisGroupId = await createAnalysisGroup({
          id: analysisGroup.UuringuGruppId,
          name: analysisGroup.UuringuGruppNimi,
          order: analysisGroup.UuringuGruppJarjekord,
        });

        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 isExistingAnalysisElement = groupExistingAnalysisElements.find(
          ({ analysis_id_original }) =>
            analysis_id_original === analysisElement.UuringId,
        );
        if (isExistingAnalysisElement) {
          console.info(
            `Analysis element '${analysisElement.UuringNimi}' already exists`,
          );
          continue;
        }

        const insertedAnalysisElementId = await createAnalysisElement({
          analysisElement: analysisElement!,
          analysisGroupId,
          materialGroups: toArray(item.MaterjalideGrupp),
        });

        if (Array.isArray(analysisElement.UuringuElement)) {
          for (const nestedAnalysisElement of analysisElement.UuringuElement) {
            await createAnalysisElement({
              analysisElement: nestedAnalysisElement,
              analysisGroupId,
              materialGroups: toArray(item.MaterjalideGrupp),
            });
          }
        }

        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) {
          const existingAnalyses = await getAnalyses({
            originalIds: analyses.map(({ UuringId }) => UuringId),
          });

          for (const analysis of analyses) {
            const isExistingAnalysis = existingAnalyses.find(
              ({ analysis_id_original }) =>
                analysis_id_original === analysis.UuringId,
            );
            if (isExistingAnalysis) {
              console.info(`Analysis '${analysis.UuringNimi}' already exists`);
              continue;
            }

            const insertedAnalysisId = await createAnalysis(
              analysis,
              analysisGroupId,
            );

            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,
                })),
              );
            }
          }
        }
      }
    }

    console.info('Inserting codes');
    await createCodes(codes);

    console.info('Inserting sync entry');
    await createSyncSuccessEntry();
  } catch (e) {
    const errorMessage = e instanceof Error ? e.message : String(e);
    await createSyncFailEntry(
      JSON.stringify(
        {
          message: errorMessage,
          stack: e instanceof Error ? e.stack : undefined,
          name: e instanceof Error ? e.name : 'Unknown',
        },
        null,
        2,
      ),
    );
    console.error('Sync failed:', e);
    throw new Error(
      `Failed to sync public message data, error: ${errorMessage}`,
    );
  }
}
