/**
 * Copyright Super iPaaS Integration LLC, an IBM Company 2024
 */
import {
  equalsIgnoreCase,
  isNullOrUndefined,
} from "../helpers/common/data-helper.js";
import { BaseAsset } from "../model/assets-model.js";
import {
  getTargetModelAssetKind,
  hasNamespace,
  isValidAsset,
  isValidAssetRefValue,
} from "../helpers/apim/asset-helper.js";
import { COLON } from "../constants/app-constants.js";
import { AssetCacheModel } from "../model/asset-cache-model.js";
import {
  fromAssetRefValue,
  isSameAsset,
} from "../helpers/apim/build-helper.js";

class AssetCache {
  private static INSTANCE: AssetCache;
  private readonly processedAssets: Set<AssetCacheModel> =
    new Set<AssetCacheModel>();
  private readonly toBeProcessedAssets: Set<AssetCacheModel> =
    new Set<AssetCacheModel>();

  private constructor() {}

  public static getInstance() {
    if (isNullOrUndefined(AssetCache.INSTANCE)) {
      AssetCache.INSTANCE = new AssetCache();
    }
    return this.INSTANCE;
  }

  public markAsProcessed(asset: BaseAsset) {
    if (!isValidAsset(asset)) {
      return;
    }

    const result: string[] = hasNamespace(asset)
      ? [asset.metadata.namespace]
      : [];

    result.push(asset.metadata.name);
    result.push(asset.metadata.version);

    const assetRefValue = result.join(COLON);
    this.processedAssets.add({
      kind: getTargetModelAssetKind(asset.kind),
      ref: assetRefValue,
      isNewlyAdded: false,
    });
    if (this.isToBeProcessed(asset.kind, assetRefValue)) {
      const unProcessedAsset = this.getUnProcessedAsset(
        asset.kind,
        assetRefValue
      ) as AssetCacheModel;
      this.toBeProcessedAssets.delete(unProcessedAsset);
    }
  }

  public isProcessed(targetKind: string, assetRefValue: string) {
    const found = this.getProcessedAsset(targetKind, assetRefValue);
    return found !== undefined;
  }

  public isToBeProcessed(targetKind: string, assetRefValue: string) {
    const found = this.getUnProcessedAsset(targetKind, assetRefValue);
    return found !== undefined;
  }

  public getNewlyAddedUnProcessedAssets(): Set<AssetCacheModel> {
    const unProcessedAssets = this.getUnProcessedAssets();
    const newlyAdded = new Set<AssetCacheModel>();
    unProcessedAssets.forEach((unProcessedAsset) => {
      if (unProcessedAsset.isNewlyAdded) {
        newlyAdded.add({
          ...unProcessedAsset,
        });
      }
    });
    return newlyAdded;
  }

  public getCheckedUnProcessedAssets() {
    const unProcessedAssets = this.getUnProcessedAssets();
    const checkedAssets = new Set<AssetCacheModel>();
    unProcessedAssets.forEach((unProcessedAsset) => {
      if (!unProcessedAsset.isNewlyAdded) {
        checkedAssets.add({
          ...unProcessedAsset,
        });
      }
    });
    return checkedAssets;
  }

  public markUnProcessedAssetAsChecked(cacheModel: AssetCacheModel) {
    const unProcessedAsset = this.getUnProcessedAsset(
      cacheModel.kind,
      cacheModel.ref
    );
    if (
      !isNullOrUndefined(unProcessedAsset) &&
      unProcessedAsset?.isNewlyAdded
    ) {
      unProcessedAsset.isNewlyAdded = false;
    }
  }

  public markAllUnProcessedAssetAsUnchecked() {
    this.toBeProcessedAssets.forEach(
      (toBeProcessed) => (toBeProcessed.isNewlyAdded = true)
    );
  }

  getProcessedAsset(
    targetKind: string,
    assetRefValue: string
  ): AssetCacheModel | undefined {
    for (const value of this.processedAssets.values()) {
      if (
        isSameAsset(
          fromAssetRefValue(value.ref),
          fromAssetRefValue(assetRefValue)
        ) &&
        equalsIgnoreCase(value.kind, getTargetModelAssetKind(targetKind))
      ) {
        return value;
      }
    }
    return undefined;
  }

  getUnProcessedAsset(targetKind: string, assetRefValue: string) {
    for (const value of this.toBeProcessedAssets.values()) {
      if (
        isSameAsset(
          fromAssetRefValue(value.ref),
          fromAssetRefValue(assetRefValue)
        ) &&
        equalsIgnoreCase(value.kind, getTargetModelAssetKind(targetKind))
      ) {
        return value;
      }
    }
    return undefined;
  }

  public getUnProcessedAssets(): Set<AssetCacheModel> {
    return new Set(this.toBeProcessedAssets);
  }

  public clear(): void {
    this.toBeProcessedAssets.clear();
    this.processedAssets.clear();
  }

  //assetRef: <namespace>:<name>:<version> or <name>:<version>
  public checkAndMarkAsUnProcessed(targetAssetModel: AssetCacheModel): boolean {
    if (!isValidAssetRefValue(targetAssetModel.ref)) {
      return false;
    }
    if (this.isProcessed(targetAssetModel.kind, targetAssetModel.ref)) {
      return false;
    }
    this.toBeProcessedAssets.add(targetAssetModel);
    return true;
  }
}

export { AssetCache };
