/**
 * Copyright IBM Corp. 2024, 2025
 */
import JSZip from 'jszip';
import yaml from 'js-yaml';
import { checkFileExtension } from '../utils.js';
import { AppConstants } from '../constants/app.constants.js';
import { YamlContent, Logger } from '@apic/studio-shared';

export class DataPowerAdapter {
  public async checkForDataPowerAssembly(buffer: Buffer): Promise<boolean> {
    const zipContent = await this.loadZipFromBuffer(buffer);
    let isdataPower = false;
    for (const fileName in zipContent.files) {
      const entry = zipContent.files[fileName];
      if (entry && !entry.dir && checkFileExtension(fileName)) {
        const content = await entry.async('string');
        isdataPower = await this.hasDataPowerAsset(content);
        if (isdataPower) {
          return true;
        }
      }
    }
    return isdataPower;
  }

  public async getDataPowerAssemblyContent(buffer: Buffer): Promise<Map<string, any>> {
    const zipContent = await this.loadZipFromBuffer(buffer);
    const policySeqToSpecRef = new Map<string, string>();
    const corsToSpecRef = new Map<string, string>();
    const propertiesToSpecRef = new Map<string, string>();

    //Collect all API references
    for (const fileName in zipContent.files) {
      const entry = zipContent.files[fileName];
      if (entry && !entry.dir && checkFileExtension(fileName)) {
        const content = await entry.async('string');
        const yamlContents = yaml.loadAll(content) as YamlContent[];
        for (const yamlContent of yamlContents) {
          if (yamlContent.kind?.toLowerCase() === 'api') {
            const specStr = JSON.stringify(yamlContent.spec);
            const specObj = yaml.load(specStr) as any;
            let apiSpecRef;
            if (
              yamlContent.metadata &&
              yamlContent.metadata.type &&
              yamlContent.metadata.type === 'SOAP'
            ) {
              apiSpecRef = specObj?.['rest-def']?.$path;
            } else {
              apiSpecRef = specObj?.['api-spec'].$path;
            }
            const policySeqRefItems = specObj?.[AppConstants.POLICY_SEQ];
            const policySeqRef = Array.isArray(policySeqRefItems)
              ? policySeqRefItems.map((e) => e.$ref)[0]
              : '';
            if (policySeqRef && apiSpecRef) {
              policySeqToSpecRef.set(policySeqRef, apiSpecRef); // Create a map for policy seq to apispec file
            }
            const corsRef = specObj?.cors?.$ref;
            if (corsRef && apiSpecRef) {
              corsToSpecRef.set(corsRef, apiSpecRef);
            }
            const propertiesRef = specObj?.properties?.$ref;
            if (propertiesRef && apiSpecRef) {
              propertiesToSpecRef.set(propertiesRef, apiSpecRef);
            }
          }
        }
      }
    }

    const specToAssemblyContentMap = new Map<string, any>();

    //Process DataPowerAssembly content
    if (policySeqToSpecRef.size > 0) {
      for (const fileName in zipContent.files) {
        const entry = zipContent.files[fileName];
        if (entry && !entry.dir && checkFileExtension(fileName)) {
          const content = await entry.async('string');
          const yamlContents = yaml.loadAll(content) as YamlContent[];
          for (const yamlContent of yamlContents) {
            // Find datapower kind file and get the dataPowerAssemblyContent
            if (yamlContent.kind?.toLowerCase() === AppConstants.DATAPOWERASSEMBLY) {
              const metadata = yamlContent.metadata;
              const dataPowerAssemblyContent = yamlContent.spec;

              if (metadata.namespace && metadata.name && metadata.version) {
                const dataPowerAssemblyRef = metadata.namespace.concat(
                  ':',
                  metadata.name,
                  ':',
                  metadata.version
                );

                // From the datapower metadata check if key exists in the policySeqToSpecRef map replace content in the spec file
                if (policySeqToSpecRef.has(dataPowerAssemblyRef)) {
                  const specfile = policySeqToSpecRef.get(dataPowerAssemblyRef);
                  if (specfile && dataPowerAssemblyContent) {
                    specToAssemblyContentMap.set(specfile, dataPowerAssemblyContent);
                  }
                }
              }
            }
          }
        }
      }
    }

    // Third pass: Process CORS content and add it to the x-ibm-configuration field
    if (corsToSpecRef.size > 0) {
      for (const fileName in zipContent.files) {
        const entry = zipContent.files[fileName];
        if (entry && !entry.dir && checkFileExtension(fileName)) {
          const content = await entry.async('string');
          const yamlContents = yaml.loadAll(content) as YamlContent[];
          for (const yamlContent of yamlContents) {
            if (yamlContent.kind?.toLowerCase() === 'cors') {
              const metadata = yamlContent.metadata;
              const corsContent = yamlContent.spec;

              if (metadata.namespace && metadata.name && metadata.version) {
                const corsRef = metadata.namespace.concat(
                  ':',
                  metadata.name,
                  ':',
                  metadata.version
                );

                // Check if this CORS reference exists in our map
                if (corsToSpecRef.has(corsRef)) {
                  const specfile = corsToSpecRef.get(corsRef);
                  if (specfile && corsContent) {
                    // If we already have DataPowerAssembly content for this spec file
                    if (specToAssemblyContentMap.has(specfile)) {
                      const existingContent = specToAssemblyContentMap.get(specfile);
                      // Add CORS content under x-ibm-configuration
                      if (existingContent && existingContent['x-ibm-configuration']) {
                        existingContent['x-ibm-configuration'].cors = {};
                        existingContent['x-ibm-configuration'].cors.enabled = true;
                        existingContent['x-ibm-configuration'].cors.policy = [
                          {
                            'allow-credentials': corsContent.rules[0].allowCredentials,
                            'allow-origin': corsContent.rules[0].originList,
                            'expose-headers': {
                              predefined: corsContent.rules[0].exposeHeaders?.predefined,
                              backend: corsContent.rules[0].exposeHeaders?.backend,
                              custom: corsContent.rules[0].exposeHeaders?.custom,
                            },
                          },
                        ];
                        specToAssemblyContentMap.set(specfile, existingContent);
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }

    // Fourth pass: Process Properties content and add it to the x-ibm-configuration field
    if (propertiesToSpecRef.size > 0) {
      for (const fileName in zipContent.files) {
        const entry = zipContent.files[fileName];
        if (entry && !entry.dir && checkFileExtension(fileName)) {
          const content = await entry.async('string');
          const yamlContents = yaml.loadAll(content) as YamlContent[];
          for (const yamlContent of yamlContents) {
            if (yamlContent.kind?.toLowerCase() === 'properties') {
              const metadata = yamlContent.metadata;
              const propertiesContent = yamlContent.spec;

              if (metadata.namespace && metadata.name && metadata.version) {
                const propertiesRef = metadata.namespace.concat(
                  ':',
                  metadata.name,
                  ':',
                  metadata.version
                );

                // Check if this Properties reference exists in our map
                if (propertiesToSpecRef.has(propertiesRef)) {
                  const specfile = propertiesToSpecRef.get(propertiesRef);
                  if (specfile && propertiesContent) {
                    // If we already have DataPowerAssembly content for this spec file
                    if (specToAssemblyContentMap.has(specfile)) {
                      const existingContent = specToAssemblyContentMap.get(specfile);
                      // Add Properties content under x-ibm-configuration
                      existingContent['x-ibm-configuration'].properties = {};
                      if (existingContent && existingContent['x-ibm-configuration']) {
                        existingContent['x-ibm-configuration'].properties = propertiesContent;
                        specToAssemblyContentMap.set(specfile, existingContent);
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }

    return specToAssemblyContentMap;
  }

  private async loadZipFromBuffer(fileBuffer: Buffer): Promise<JSZip> {
    Logger.info('Loading ZIP from buffer');
    const zip = new JSZip();
    return zip.loadAsync(fileBuffer);
  }

  private async hasDataPowerAsset(content: string): Promise<boolean> {
    const yamlContents = yaml.loadAll(content) as YamlContent[];
    for (const yamlContent of yamlContents) {
      const isDataPower =
        yamlContent.kind && yamlContent.kind.toLowerCase() == AppConstants.DATAPOWERASSEMBLY;
      if (isDataPower) {
        return isDataPower;
      }
    }
    return false;
  }
}
