/**
 * Copyright IBM Corp. 2024, 2025
 */
import {
  checkFileExtension,
  isRelativePath,
  isValidAsset,
  SpecObject,
  ZipProcessor,
} from '../index.js';
import { YamlContent, Logger } from '@apic/studio-shared';
import { BuildProjectAssets } from '../build-project-assets.js';
import yaml from 'js-yaml';
import path from 'path';
import JSZip from 'jszip';
import { AppConstants } from '../constants/app.constants.js';

export const processGatewayJson = async (fileBuffer: Buffer) => {
  Logger.info('Starting extraction of gateway JSON.');

  const obj = new ZipProcessor();
  return obj.extractGatewaysJson(fileBuffer);
};

export const normalizeZipPaths = async (zip: JSZip) => {
  Logger.debug('Starting path normalization in zip file.');

  const normalizedZip = new JSZip();
  await Promise.all(
    Object.values(zip.files).map(async (file) => {
      const normalizedPath = path.normalize(file.name);
      if (file.dir) {
        normalizedZip.folder(normalizedPath);
      } else {
        const fileData = await file.async('nodebuffer');
        normalizedZip.file(normalizedPath, fileData);
        Logger.debug(`Normalized file: ${normalizedPath}`);
      }
    })
  );

  Logger.debug('Path normalization completed.');
  return normalizedZip;
};
export const updateRelativePath = async (
  asset: YamlContent,
  basePath: string
): Promise<YamlContent> => {
  const extract = (obj: SpecObject) => {
    for (const key in obj) {
      const value = obj[key];
      if (key === AppConstants.pathVariable && typeof value === 'string') {
        if (isRelativePath(value)) {
          const baseDir = path.dirname(basePath);
          const resolvedPath = path.join(baseDir, value);
          const normalizedPath = path.normalize(resolvedPath).replace(/\\+/g, '/');
          obj[key] = normalizedPath.slice(normalizedPath.indexOf('/') + 1);
        }
      } else if (typeof value === 'object' && value !== null) {
        extract(value);
      }
    }
  };

  if (asset.spec) {
    extract(asset.spec as unknown as SpecObject);
  }

  return asset;
};

export const updatePathValueInContent = async (content: string, basePath: string) => {
  const parsedObjs = yaml.loadAll(content) as YamlContent[];
  const updatedObjs = await Promise.all(
    parsedObjs.map(async (obj) => {
      if (isValidAsset(obj)) {
        return await updateRelativePath(obj, basePath);
      }
      return obj;
    })
  );
  return updatedObjs.map((obj) => yaml.dump(obj)).join('---\n');
};

export const resolveRelativePaths = async (zipBuffer: Buffer) => {
  Logger.debug('Starting path normalization in zip file.');
  const zip = await JSZip.loadAsync(zipBuffer);

  const resolvedZip = new JSZip();
  await Promise.all(
    Object.values(zip.files).map(async (file) => {
      if (file.dir) {
        resolvedZip.folder(file.name);
        Logger.debug(`Resolved directory: ${file.name}`);
      } else {
        const nameArray = file.name.split(path.sep);
        const name = nameArray[nameArray.length - 1];

        if (name && checkFileExtension(name)) {
          const content = await file.async('string');

          if (!content || content.trim().length === 0) {
            Logger.info(`Skipping empty file: ${file.name}`);
            return;
          }

          const updatedContent = await updatePathValueInContent(content, file.name);
          resolvedZip.file(file.name, updatedContent);
        } else {
          const fileData = await file.async('nodebuffer');
          resolvedZip.file(file.name, fileData);
        }
      }
    })
  );

  Logger.debug('Path normalization completed.');
  return resolvedZip;
};

export const posixNormalization = async (zip: JSZip) => {
  const normalizedZip = new JSZip();
  await Promise.all(
    Object.values(zip.files).map(async (file) => {
      const normalizedPath = file.name.replace(/\\/g, '/').replace(/\/+/g, '/');
      if (file.dir) {
        normalizedZip.folder(normalizedPath);
        Logger.debug(`POSIX normalized directory: ${normalizedPath}`, {
          code: '0301',
          type: 'directory',
        });
      } else {
        const fileData = await file.async('nodebuffer');
        normalizedZip.file(normalizedPath, fileData);
        Logger.debug(`POSIX normalized file: ${normalizedPath}`, {
          code: '0301',
          type: 'file',
        });
      }
    })
  );

  return normalizedZip;
};

export const processProjectBuild = async (fileBuffer: Buffer, _mode: string) => {
  Logger.info('Starting project build processing.');
  try {
    const zip = await JSZip.loadAsync(fileBuffer);
    Logger.debug('Loading and normalizing zip paths.');
    const normalizedZip = await normalizeZipPaths(zip);
    const normalizedBuffer = await normalizedZip.generateAsync({
      type: 'nodebuffer',
    });
    const resolvedZip = await resolveRelativePaths(normalizedBuffer);
    Logger.debug('Normalized zip buffer generated.');
    const obj = new BuildProjectAssets();
    Logger.info('Processing normalized project zip.');
    const { zip: result, errors } = await obj.processProjectZip(
      await resolvedZip.generateAsync({
        type: 'nodebuffer',
      }),
      _mode
    );

    if (!result) {
      const errorDetail = errors?.[0] || '';
      Logger.error(`Project build processing failed. Error: ${errorDetail}`, undefined, {
        code: '0003',
      });

      let errorMessage = '';
      if (errorDetail) {
        errorMessage = `${errorDetail}. Review the error and try publishing again.`;
      } else {
        errorMessage = `Project build failed. Check your configuration and try again.`;
      }

      // Return standardized error response
      return {
        success: false,
        statusCode: 400,
        message: 'Project build validation failed',
        data: null,
        errors: [errorMessage],
      };
    }

    Logger.info('Project build processing succeeded.');
    const temporary_zip = await posixNormalization(result);
    const zipBuffer = await temporary_zip.generateAsync({ type: 'nodebuffer' });

    // Return successful response with the zip buffer
    return {
      success: true,
      statusCode: 200,
      message: 'Project build successful',
      data: zipBuffer,
      errors: [],
    };
  } catch (error) {
    // Handle unexpected errors
    const errorMessage =
      error instanceof Error
        ? error.message
        : `Project build failed. The project has problems we couldn't identify.`;
    Logger.error(
      `Unexpected error in project build: ${errorMessage}`,
      error instanceof Error ? error : new Error(String(error))
    );

    return {
      success: false,
      statusCode: 500,
      message: 'Project build validation failed',
      data: null,
      errors: [errorMessage],
    };
  }
};
