import type { AddOn } from '../models/pricing2yaml/addon';
import type { AutomationType, Feature, IntegrationType, PaymentType } from '../../types';
import type { Plan } from '../models/pricing2yaml/plan';
import type { Pricing } from '../models/pricing2yaml/pricing';
import type {
  FeatureType,
  RenderMode,
  UsageLimitType,
  ValueType,
} from '../models/pricing2yaml/types';
import type { ContainerUsageLimits, UsageLimit } from '../models/pricing2yaml/usage-limit';
import * as cc from 'currency-codes';
import { isAutomationType, isIntegrationType } from '../models/pricing2yaml/feature';

const VERSION_REGEXP = /^\d+\.\d+$/;
const unlimitedValue = 100000000;

export function validateName(name: string | null, item: string): string {
  if (name === null || name === undefined) {
    throw new Error(`The ${item} must have a name`);
  }

  if (typeof name !== 'string') {
    throw new TypeError(`The ${item} name must be a string`);
  }

  const trimmedName = name.trim();

  if (trimmedName.length === 0) {
    throw new Error(`The ${item} name must not be empty`);
  }

  if (trimmedName.length < 3) {
    throw new Error(`The ${item} name must have at least 3 characters`);
  }

  if (trimmedName.length > 255) {
    throw new Error(`The ${item} name must have at most 255 characters`);
  }

  return trimmedName;
}

export function validateSyntaxVersion(version: string): string {
  if (version === null || version === undefined) {
    throw new Error(
      `The syntaxVersion field of the pricing must not be null or undefined. Please ensure that the syntaxVersion field is present and correctly formatted`
    );
  }

  if (typeof version !== 'string' || !VERSION_REGEXP.test(version)) {
    throw new TypeError(
      `The syntaxVersion field of the pricing does not follow the required structure: X.Y (being X and Y numbers). Please ensure it is a string in the format X.Y`
    );
  }

  return version;
}

export function validateVersion(version: string | undefined, createdAt: Date): string {
  version ??= `${createdAt.getFullYear()}-${createdAt.getMonth() + 1}-${createdAt.getDate()}`;

  if (typeof version !== 'string') {
    throw new TypeError(
      `The version field of the pricing must be a string. Please ensure that the version field is present and correctly formatted`
    );
  }

  return version;
}

export function validateCreatedAt(createdAt: string | Date | null): Date {
  if (createdAt === null || createdAt === undefined) {
    throw new Error(
      `The createdAt field must not be null or undefined. Please ensure that the createdAt field is present and correctly formatted (as Date or string)`
    );
  }

  if (typeof createdAt === 'string'){
    if (/^\d{4}-\d{2}-\d{2}$/.test(createdAt)) {
      createdAt = new Date(createdAt);
    } else {
      throw new TypeError(
      `The createdAt field must be a string in the format yyyy-mm-dd or a valid Date object`
      );
    }
  }
  
  if (!(createdAt instanceof Date) || isNaN(createdAt.getTime())) {
    throw new TypeError(
      `The createdAt field must be a valid Date object or a string in a recognized date format`
    );
  }

  const now = new Date();
  if (createdAt > now) {
    throw new Error(`The createdAt field must not be a future date`);
  }

  return createdAt;
}

export function validateCurrency(currency: string | null): string {
  if (currency === null || currency === undefined) {
    throw new Error(
      `The currency field of the pricing must not be null or undefined. Please ensure that the currency field is present and correctly formatted`
    );
  }

  if (typeof currency !== 'string') {
    throw new TypeError(`The currency field of the pricing must be a string`);
  }

  const trimmedCurrency = currency.trim();

  if (trimmedCurrency.length === 0) {
    throw new Error(`The currency field of the pricing must not be empty`);
  }

  const currencyCode = cc.code(trimmedCurrency);

  if (!currencyCode) {
    throw new Error(`The currency code ${trimmedCurrency} is not a valid ISO 4217 currency code`);
  }

  return currency;
}

export function validateDescription(description: string | null | undefined): string | undefined {
  description ??= undefined;

  return description;
}

export function validateValueType(valueType: string | null): ValueType {
  if (valueType === null || valueType === undefined) {
    throw new Error(
      `The valueType field of a feature must not be null or undefined. Please ensure that the valueType field is present and it's value correspond to either BOOLEAN, NUMERIC or TEXT`
    );
  }

  if (typeof valueType !== 'string') {
    throw new TypeError(
      `The valueType field of a feature must be a string, and its value must be either BOOLEAN, NUMERIC or TEXT. Received: ${valueType} `
    );
  }

  valueType = valueType.trim().toUpperCase();

  if (!['NUMERIC', 'BOOLEAN', 'TEXT'].includes(valueType)) {
    throw new Error(
      `The valueType field of a feature must be one of NUMERIC, BOOLEAN, or TEXT. Received: ${valueType}`
    );
  }

  return valueType as ValueType;
}

export function validateDefaultValue(
  elem: Feature | UsageLimit,
  item: string
): number | boolean | string | PaymentType[] {
  if (elem.defaultValue === null || elem.defaultValue === undefined) {
    throw new Error(
      `The defaultValue field of a ${item} must not be null or undefined. Please ensure that the defaultValue field is present and its defaultValue type correspond to the declared valueType`
    );
  }

  switch (elem.valueType) {
    case 'NUMERIC':
      if (typeof elem.defaultValue !== 'number') {
        throw new TypeError(
          `The defaultValue field of a ${item} must be a number when its valueType is NUMERIC. Received: ${elem.defaultValue}`
        );
      }
      if (elem.defaultValue > unlimitedValue){
        elem.defaultValue = unlimitedValue;
      }
      break;
    case 'BOOLEAN':
      if (typeof elem.defaultValue !== 'boolean') {
        throw new TypeError(
          `The defaultValue field of a ${item} must be a boolean when its valueType is BOOLEAN. Received: ${elem.defaultValue}`
        );
      }
      break;
    case 'TEXT':
      if (elem.type === 'PAYMENT') {
        if (!(elem.defaultValue instanceof Array)) {
          throw new TypeError(`Payment method value must be an array of payment methods`);
        }
        for (const paymentMethod of elem.defaultValue) {
          if (
            !['CARD', 'GATEWAY', 'INVOICE', 'ACH', 'WIRE_TRANSFER', 'OTHER'].includes(paymentMethod)
          ) {
            throw new Error(
              `Invalid payment method: ${paymentMethod}. Please provide one of the following: CARD, GATEWAY, INVOICE, ACH, WIRE_TRANSFER, OTHER`
            );
          }
        }
        break;
      }
      if (typeof elem.defaultValue !== 'string') {
        throw new Error(
          `The defaultValue field of a ${item} must be a string when its valueType is TEXT. Received: ${elem.defaultValue}`
        );
      }
      break;
    default:
      throw new Error(
        `The valueType field of a ${item} must be either BOOLEAN, NUMERIC or TEXT. Received: ${elem.valueType}`
      );
  }

  return elem.defaultValue;
}

export function validateExpression(
  expression: string | null | undefined,
  item: string
): string | undefined {
  expression ??= undefined;

  if (typeof expression === 'string') {
    if (expression.trim().length === 0) {
      throw new Error(
        `The ${item} field of a feature must not be empty. If you don't want to declare an expression for this feature, either use null or undefined`
      );
    }
  }

  return expression;
}

export function validateValue(
  elem: Feature | UsageLimit,
  item: string,
  valueType: ValueType | undefined = undefined
): number | boolean | string | PaymentType[] | undefined {
  elem.value ??= undefined;

  const valueTypeToValidate = valueType || elem.valueType;

  switch (valueTypeToValidate) {
    case 'NUMERIC':
      if (typeof elem.value !== 'number' && elem.value !== undefined) {
        throw new TypeError(
          `The value field of a ${item} must be a number when its valueType is NUMERIC. Received: ${elem.value}`
        );
      }
      if (elem.value! > unlimitedValue){
        elem.value = unlimitedValue;
      }
      break;
    case 'BOOLEAN':
      if (typeof elem.value !== 'boolean' && elem.value !== undefined) {
        throw new TypeError(
          `The value field of a ${item} must be a boolean when its valueType is BOOLEAN. Received: ${elem.value}`
        );
      }
      break;
    case 'TEXT':
      if (elem.type === 'PAYMENT' && elem.value !== undefined) {
        if (!(elem.value instanceof Array)) {
          throw new TypeError(`Payment method value must be an array of payment methods`);
        }
        for (const paymentMethod of elem.value) {
          if (
            !['CARD', 'GATEWAY', 'INVOICE', 'ACH', 'WIRE_TRANSFER', 'OTHER'].includes(paymentMethod)
          ) {
            throw new Error(
              `Invalid payment method: ${paymentMethod}. Please provide one of the following: CARD, GATEWAY, INVOICE, ACH, WIRE_TRANSFER, OTHER`
            );
          }
        }
        break;
      }
      if (typeof elem.value !== 'string' && elem.value !== undefined) {
        throw new TypeError(
          `The value field of a ${item} must be a string when its valueType is TEXT. Received: ${elem.value}`
        );
      }
      break;
    default:
      throw new TypeError(
        `The valueType field of a ${item} must be either BOOLEAN, NUMERIC or TEXT. Received: ${elem.valueType}`
      );
  }

  return elem.value;
}

export function validateFeatureType(type: string | null | undefined): FeatureType {
  if (type === null || type === undefined) {
    throw new Error(
      `The type field of a feature must not be null or undefined. Please ensure that the type field is present and it's value correspond to either INFORMATION, INTEGRATION, DOMAIN, AUTOMATION, MANAGEMENT, GUARANTEE, SUPPORT or PAYMENT`
    );
  }

  if (typeof type !== 'string') {
    throw new TypeError(
      `The type field of a feature must be a string, and its value must be either INFORMATION, INTEGRATION, DOMAIN, AUTOMATION, MANAGEMENT, GUARANTEE, SUPPORT or PAYMENT. Received: ${type} `
    );
  }

  type = type.trim().toUpperCase();

  if (
    ![
      'INFORMATION',
      'INTEGRATION',
      'DOMAIN',
      'AUTOMATION',
      'MANAGEMENT',
      'GUARANTEE',
      'SUPPORT',
      'PAYMENT',
    ].includes(type)
  ) {
    throw new Error(
      `The type field of a feature must be one of INFORMATION, INTEGRATION, DOMAIN, AUTOMATION, MANAGEMENT, GUARANTEE, SUPPORT or PAYMENT. Received: ${type}`
    );
  }

  return type as FeatureType;
}

export function validateFeatureIntegrationType(
  integrationType: IntegrationType | null | undefined,
  featureType: FeatureType
) {
  if (integrationType === null || integrationType === undefined) {
    integrationType = undefined;
  }

  if (integrationType && typeof integrationType !== 'string') {
    throw new TypeError(
      `The integrationType field of a feature must be an IntegrationType ('API' | 'EXTENSION' | 'IDENTITY_PROVIDER' | 'WEB_SAAS' | 'MARKETPLACE' | 'EXTERNAL_DEVICE'). Received: ${integrationType}`
    );
  }else if (featureType === 'INTEGRATION' && !integrationType) {
    throw new Error(
      `The integrationType field of a feature of type INTEGRATION must not be null or undefined. Please ensure that the integrationType field is present and it's value correspond to either API, EXTENSION, IDENTITY_PROVIDER, WEB_SAAS, MARKETPLACE or EXTERNAL_DEVICE`
    );
  }else if (integrationType && typeof integrationType === "string" && !isIntegrationType(integrationType)){
    throw new Error(
      `The integrationType field of a feature must be one of API, EXTENSION, IDENTITY_PROVIDER, WEB_SAAS, MARKETPLACE or EXTERNAL_DEVICE. Received: ${integrationType}`
    );
  }

  return integrationType;
}

export function validateFeatureAutomationType(automationType: AutomationType | null | undefined, featureType: FeatureType) {
  if (automationType === null || automationType === undefined) {
    automationType = undefined;
  }

  if (automationType && typeof automationType !== 'string') {
    throw new TypeError(
      `The automationType field of a feature must be an AutomationType ('BOT' | 'FILTERING' | 'TRACKING' | 'TASK_AUTOMATION'). Received: ${automationType}`
    );
  } else if (featureType === 'AUTOMATION' && !automationType) {
    throw new Error(
      `The automationType field of a feature of type AUTOMATION must not be null or undefined. Please ensure that the automationType field is present and its value corresponds to either BOT, FILTERING, TRACKING, or TASK_AUTOMATION`
    );
  } else if (automationType && typeof automationType === "string" && !isAutomationType(automationType)) {
    throw new Error(
      `The automationType field of a feature must be one of BOT, FILTERING, TRACKING, or TASK_AUTOMATION. Received: ${automationType}`
    );
  }

  return automationType;
}

export function validateUnit(unit: string | null | undefined): string {
  if (unit === null || unit === undefined) {
    unit = '';
  }

  if (typeof unit !== 'string') {
    throw new TypeError(`The unit field of a usage limit must be a string. Received: ${unit} `);
  }

  return unit;
}

export function validateUsageLimitType(type: string | null | undefined): UsageLimitType {
  if (type === null || type === undefined) {
    throw new Error(
      `The type field of a usage limit must not be null or undefined. Please ensure that the type field is present and it's value correspond to either RENEWABLE or NON_RENEWABLE`
    );
  }

  if (typeof type !== 'string') {
    throw new TypeError(
      `The type field of a usage limit must be a string, and its value must be either RENEWABLE or NON_RENEWABLE. Received: ${type} `
    );
  }

  type = type.trim().toUpperCase();

  if (!['RENEWABLE', 'NON_RENEWABLE'].includes(type)) {
    throw new Error(
      `The type field of a usage limit must be one of RENEWABLE or NON_RENEWABLE. Received: ${type}`
    );
  }

  return type as UsageLimitType;
}

export function validateLinkedFeatures(
  linkedFeatures: string[] | undefined | null,
  pricing: Pricing
): string[] | undefined {
  linkedFeatures ??= undefined;

  // Check if linked features is an array
  if (Array.isArray(linkedFeatures)) {
    const pricingFeatures = Object.values(pricing.features).map(f => f.name);

    for (const featureName of linkedFeatures) {
      if (!pricingFeatures.includes(featureName)) {
        throw new Error(
          `The feature ${featureName}, declared as a linked feature for an usage limit, is not defined in the global features`
        );
      }
    }
  }

  return linkedFeatures;
}

export function validateRenderMode(renderMode: string | undefined | null): RenderMode {
  if (renderMode === null || renderMode === undefined) {
    renderMode ??= 'AUTO';
  }

  renderMode = renderMode.toUpperCase();

  if (!['AUTO', 'ENABLED', 'DISABLED'].includes(renderMode)) {
    throw new Error(
      `The render field of a feature or usage limit must be one of AUTO, ENABLED or DISABLED. Received: ${renderMode}`
    );
  }

  return renderMode as RenderMode;
}

export function validatePlanFeatures(
  plan: Plan,
  planFeatures: Record<string, Feature>
): Record<string, Feature> {
  const featuresModifiedByPlan = plan.features;
  plan.features = planFeatures;

  for (const planFeature of Object.values(featuresModifiedByPlan)) {
    try {
      if (!Object.values(planFeatures).some(f => f.name === planFeature.name)) {
        throw new Error(`Feature ${planFeature.name} is not defined in the global features.`);
      }

      const featureWithDefaultValue = Object.values(plan.features).find(
        f => f.name === planFeature.name
      ) as Feature;

      featureWithDefaultValue.value = planFeature.value;
      featureWithDefaultValue.value = validateValue(featureWithDefaultValue, 'feature');
    } catch (err) {
      throw new Error(
        `Error while parsing the feature ${planFeature.name} of the plan ${plan.name}. Error: ${
          (err as Error).message
        }`
      );
    }
  }

  return plan.features;
}

export function validatePlanUsageLimits(
  plan: Plan,
  planUsageLimits: ContainerUsageLimits
): ContainerUsageLimits {
  const usageLimitsModifiedByPlan = plan.usageLimits!;
  plan.usageLimits = planUsageLimits;

  for (const planUsageLimit of Object.values(usageLimitsModifiedByPlan)) {
    try {
      if (!Object.values(planUsageLimits).some(l => l.name === planUsageLimit.name)) {
        throw new Error(
          `Usage limit ${planUsageLimit.name} is not defined in the global usage limits.`
        );
      }

      const globalUsageLimit = Object.values(planUsageLimits).find(
        l => l.name === planUsageLimit.name
      ) as UsageLimit;

      globalUsageLimit.value = planUsageLimit.value;
      globalUsageLimit.value = validateValue(globalUsageLimit, 'usage limit') as string | number | boolean | undefined;
    } catch (err) {
      throw new Error(
        `Error while parsing the usage limit ${planUsageLimit.name} of the plan ${
          plan.name
        }. Error: ${(err as Error).message}`
      );
    }
  }

  return plan.usageLimits;
}

export function validatePrice(
  price: number | string | undefined | null,
  variables: { [key: string]: any } = {}
): number | string {
  if (price === null || price === undefined) {
    throw new Error(
      `The price field must not be null or undefined. Please ensure that the price field is present and it's a number`
    );
  }

  if (typeof price !== 'string' && typeof price !== 'number') {
    throw new TypeError(
      `The price field must be a number or a string (which can contain a formula). Received: ${price}`
    );
  }

  if (typeof price === 'number' && price < 0) {
    throw new Error(`The price field must be a positive number. Received: ${price} `);
  }

  if (typeof price === 'string') {
    if (price.includes('#')) {
      for (const [variable, value] of Object.entries(variables)) {
        let replacement: string;

        if (typeof value === 'string') {
          const escaped = (value as string).replace(/'/g, "\\'");
          replacement = `'${escaped}'`;
        } else if (value === null) {
          replacement = 'null';
        } else if (typeof value === 'object') {
          // Use JSON.stringify and wrap in parentheses so object/array literals
          // evaluate correctly inside eval (e.g. ({"test":"a"}).test )
          replacement = `(${JSON.stringify(value)})`;
        } else {
          replacement = String(value);
        }

        price = price.replace(new RegExp(`#${variable}`, 'g'), replacement);
      }

      try {
        // eslint-disable-next-line no-eval
        const evaluatedPrice = eval(price);
        if (typeof evaluatedPrice !== 'number' || isNaN(evaluatedPrice)) {
          throw new Error(
            `The evaluated price must result in a valid number. Current result after evaluation: ${evaluatedPrice}`
          );
        }
        price = evaluatedPrice;
      } catch (err) {
        throw new Error(`Error evaluating the price formula: ${(err as Error).message}`);
      }
    } else if (price.match(/^[0-9]+(\.[0-9]+)?$/)) {
      price = parseFloat(price);
    }
  }

  return price;
}

export function validateAddonFeatures(
  addon: AddOn,
  addOnFeatures: Record<string, Feature>
): Record<string, Feature> {
  for (const addOnFeature of Object.values(addon.features!)) {
    try {
      if (!Object.values(addOnFeatures).some(f => f.name === addOnFeature.name)) {
        throw new Error(`Feature ${addOnFeature.name} is not defined in the global features.`);
      }

      addon.features![addOnFeature.name].value = addOnFeature.value;
      addon.features![addOnFeature.name].value = validateValue(
        addon.features![addOnFeature.name],
        'feature',
        addOnFeatures[addOnFeature.name].valueType
      );
    } catch (err) {
      throw new Error(
        `Error while parsing the feature ${addOnFeature.name} of the plan ${addon.name}. Error: ${
          (err as Error).message
        }`
      );
    }
  }

  return addon.features as Record<string, Feature>;
}

export function validateAddonUsageLimits(
  addon: AddOn,
  addonUsageLimits: ContainerUsageLimits
): ContainerUsageLimits {
  for (const addonUsageLimit of Object.values(addon.usageLimits!)) {
    try {
      if (!Object.values(addonUsageLimits).some(l => l.name === addonUsageLimit.name)) {
        throw new Error(
          `Usage limit ${addonUsageLimit.name} is not defined in the global usage limits.`
        );
      }
      if (!('value' in addonUsageLimit)) {
        throw new Error(
          "When declaring a new value for an usage limit or a usage limit extension within an add-on, it must be provided through the 'value' field"
        );
      }
      addon.usageLimits![addonUsageLimit.name].value = addonUsageLimit.value;
      addon.usageLimits![addonUsageLimit.name].value = validateValue(
        addon.usageLimits![addonUsageLimit.name],
        'usage limit',
        addonUsageLimits[addonUsageLimit.name].valueType
      ) as string | number | boolean | undefined;
    } catch (err) {
      throw new Error(
        `Error while parsing the usage limit ${addonUsageLimit.name} of the add-on ${
          addon.name
        }. Error: ${(err as Error).message}`
      );
    }
  }

  return addon.usageLimits as ContainerUsageLimits;
}

export function validateAddonUsageLimitsExtensions(
  addon: AddOn,
  addonUsageLimits: ContainerUsageLimits
): ContainerUsageLimits {
  for (const addonUsageLimitExtension of Object.values(addon.usageLimitsExtensions!)) {
    try {
      if (!Object.values(addonUsageLimits).some(l => l.name === addonUsageLimitExtension.name)) {
        throw new Error(
          `Usage limit ${addonUsageLimitExtension.name} is not defined in the global usage limits.`
        );
      }
      if (!('value' in addonUsageLimitExtension)) {
        throw new Error(
          "When declaring a new value for an usage limit or a usage limit extension within an add-on, it must be provided through the 'value' field"
        );
      }
      addon.usageLimitsExtensions![addonUsageLimitExtension.name].value =
        addonUsageLimitExtension.value;
      addon.usageLimitsExtensions![addonUsageLimitExtension.name].value = validateValue(
        addon.usageLimitsExtensions![addonUsageLimitExtension.name],
        'usage limit',
        addonUsageLimits[addonUsageLimitExtension.name].valueType
      ) as string | number | boolean | undefined;
    } catch (err) {
      throw new Error(
        `Error while parsing the usage limit ${addonUsageLimitExtension.name} of the add-on ${
          addon.name
        }. Error: ${(err as Error).message}`
      );
    }
  }

  return addon.usageLimitsExtensions as ContainerUsageLimits;
}

export function validateAvailableFor(
  availableFor: string[] | undefined | null,
  pricing: Pricing
): string[] {
  const planNames = pricing.plans ? Object.values(pricing.plans).map(p => p.name) : [];

  availableFor ??= planNames as string[];

  if (!Array.isArray(availableFor)) {
    throw new TypeError(
      `The availableFor field must be an array of the plan names for which the addon can be contracted. Received: ${availableFor}`
    );
  }

  for (const planName of availableFor) {
    if (!planNames.includes(planName)) {
      throw new Error(`The plan ${planName} is not defined in the pricing.`);
    }
  }

  return availableFor;
}

export function validateDependsOnOrExcludes(
  fieldValue: string[] | undefined | null,
  pricing: Pricing,
  fieldType: 'dependsOn' | 'excludes'
): string[] {
  const addonNames = pricing.addOns ? Object.values(pricing.addOns).map(a => a.name) : [];

  fieldValue ??= [];

  if (!Array.isArray(fieldValue)) {
    throw new TypeError(
      `The ${fieldType} field must be an array of the addons required to contract the addon. Received: ${fieldValue}`
    );
  }

  return fieldValue;
}

export function postValidateDependsOnOrExclude(
  fieldValue: string[] | undefined,
  pricing: Pricing
): void {
  const addonNames = pricing.addOns ? Object.values(pricing.addOns).map(a => a.name) : [];

  if (!fieldValue) return;

  for (const addonName of fieldValue) {
    if (!addonNames.includes(addonName)) {
      throw new Error(`The addon ${addonName} is not defined in the pricing.`);
    }
  }
}

export function validateTags(tags: string[] | undefined): string[] {
  tags ??= [];

  if (!Array.isArray(tags) || tags.some(tag => typeof tag !== 'string')) {
    throw new TypeError(`The tags field must be an array of strings.`);
  }

  return tags;
}

export function validatePlan(plan: Plan) {
  if (typeof plan === 'object' && 'features' in plan) {
    return;
  } else {
    throw new TypeError(`The plan must be an object of type Plan`);
  }
}

export function validatePlans(plans: object) {
  if (!(typeof plans === 'object')) {
    throw new TypeError(`The plans field must be a map of Plan objects`);
  }
}

export function validateFeatures(features: object) {
  if (!(typeof features === 'object') || features === null || features === undefined) {
    throw new TypeError(`The features field must be a map of Feature objects`);
  }
}

export function validateFeature(feature: Feature) {
  if (
    typeof feature === 'object' &&
    'type' in feature &&
    'valueType' in feature &&
    'defaultValue' in feature
  ) {
    return;
  } else {
    throw new TypeError(`The feature must be an object of type Feature`);
  }
}

export function validateUsageLimits(usageLimits: object) {
  if (!(typeof usageLimits === 'object')) {
    throw new TypeError(`The usageLimits field must be a map of UsageLimit objects or undefined`);
  }
}

export function validateUsageLimit(usageLimit: UsageLimit) {
  if (
    typeof usageLimit === 'object' &&
    'type' in usageLimit &&
    'valueType' in usageLimit &&
    'defaultValue' in usageLimit
  ) {
    return;
  } else {
    throw new TypeError(`The usage limit must be an object of type UsageLimit`);
  }
}

export function validateBilling(billing: { [key: string]: number } | undefined) {
  if (billing === undefined || billing === null) {
    billing ??= {
      monthly: 1,
    };
  }

  if (!(typeof billing === 'object')) {
    throw new TypeError(`The billing field must be an object of type {[key: string]: number}`);
  }

  for (const [key, value] of Object.entries(billing)) {
    if (typeof value !== 'number') {
      throw new TypeError(`The billing entry for ${key} must be a number. Received: ${value}`);
    }

    if (value <= 0 || value > 1) {
      throw new Error(
        `The billing entry for ${key} must be a value in the range (0,1]. Received: ${value}`
      );
    }
  }

  return billing;
}

export function validateVariables(
  variables: { [key: string]: any } | undefined
): { [key: string]: any } {

  variables ??= {};

  if (typeof variables !== 'object') {
    throw new TypeError(
      `The 'variables' field must be an object of type {[key: string]: any}`
    );
  }

  // Allow any value types for variables (object, array, string, number, boolean, etc.)
  return variables;
}

export function validateUrl(url: string | undefined) {
  if (url === undefined || url === null) {
    url ??= undefined;
    return;
  }

  if (typeof url !== 'string') {
    throw new TypeError(`The url field must be a string. Received: ${url}`);
  }

  const urlPattern = /^(https?):\/\/[^\s\/$.?#].[^\s]*$/i;
  if (!urlPattern.test(url)) {
    throw new Error(
      `The url field must be a valid URL with the http or https protocol. Received: ${url}`
    );
  }

  return url;
}

export function validatePrivate(isPrivate: boolean | undefined) {
  if (isPrivate === undefined || isPrivate === null) {
    isPrivate ??= false;
  }

  if (typeof isPrivate !== 'boolean') {
    throw new TypeError(`The private field must be a boolean. Received: ${isPrivate}`);
  }

  return isPrivate;
}

export function validatePricingUrls(
  featureType: FeatureType,
  featureIntegrationType: IntegrationType | undefined,
  pricingUrls: string[] | undefined
) {
  pricingUrls ??= undefined;

  if (pricingUrls !== undefined) {
    if (!Array.isArray(pricingUrls) || pricingUrls.some(url => typeof url !== 'string')) {
      throw new TypeError(`The pricingUrls field must be an array of strings.`);
    }

    for (const url of pricingUrls) {
      const urlPattern = /^(https?):\/\/[^^\s\/$.?#].[^\s]*$/i;
      if (!urlPattern.test(url)) {
      throw new Error(`The pricingUrls field must contain only valid URLs. Invalid URL: ${url}`);
      }
    }

    if (featureType !== 'INTEGRATION' || featureIntegrationType !== 'WEB_SAAS') {
      console.log(
        "[WARNING] The pricingUrls field is only valid for features of 'type' INTEGRATION and 'integrationType' WEB_SAAS. The field will be ignored."
      );
      pricingUrls = undefined;
    }
  }

  return pricingUrls;
}

export function validateDocUrl(featureType: FeatureType, docUrl: string | undefined) {
  docUrl ??= undefined;

  if (docUrl !== undefined) {
    if (typeof docUrl !== 'string') {
      throw new TypeError(`The docUrl field must be a string.`);
    }

    const urlPattern = /^(https?):\/\/[^^\s\/$.?#].[^\s]*$/i;
    if (!urlPattern.test(docUrl)) {
      throw new Error(`The docUrl field must be a valid URL with the http or https protocol. Received: ${docUrl}`);
    }

    if (featureType !== 'GUARANTEE') {
      console.log(
        "[WARNING] The docUrl field is only valid for features of 'type' GUARANTEE. The field will be ignored."
      );
      docUrl = undefined;
    }
  }

  return docUrl;
}

export function validatePeriodUnit(periodUnit: string | undefined): "SEC" | "MIN" | "HOUR" | "DAY" | "MONTH" | "YEAR" {
  periodUnit ??= 'MONTH'; 
  
  if (typeof periodUnit !== 'string') {
    throw new TypeError(`The periodUnit field must be one of the following string values: "SEC" | "MIN" | "HOUR" | "DAY" | "MONTH" | "YEAR". Received: ${periodUnit}`);
  }

  periodUnit = periodUnit.toUpperCase();

  if (!["SEC", "MIN", "HOUR", "DAY", "MONTH", "YEAR"].includes(periodUnit)) {
    throw new Error(
      `The periodUnit field must be one of "SEC" | "MIN" | "HOUR" | "DAY" | "MONTH" | "YEAR". Received: ${periodUnit}`
    );
  }

  return periodUnit as "SEC" | "MIN" | "HOUR" | "DAY" | "MONTH" | "YEAR";
}

export function validatePeriodValue(periodValue: number | undefined): number {
  periodValue ??= 1;

  if (typeof periodValue !== 'number') {
    throw new TypeError(`The periodValue field must be a number. Received: ${periodValue}`);
  }

  if (periodValue <= 0) {
    throw new Error(`The periodValue field must be a positive number. Received: ${periodValue}`);
  }

  return periodValue;
}

export function validateTrackable(trackable: boolean | undefined): boolean {
  trackable ??= false;

  if (typeof trackable === 'string'){
    trackable = (trackable as string).toLowerCase() === 'true';
  }

  if (typeof trackable !== 'boolean') {
    throw new TypeError(`The trackable field must be a boolean. Received: ${trackable}`);
  }

  return trackable;
}

export function validateSubscriptionConstraintMinQuantity(
  subscriptionConstraintMinQuantity: number | undefined
): number {
  subscriptionConstraintMinQuantity ??= 1;

  if (typeof subscriptionConstraintMinQuantity !== 'number') {
    throw new TypeError(`The subscriptionConstraintMinQuantity field must be a number. Received: ${subscriptionConstraintMinQuantity}`);
  }

  if (subscriptionConstraintMinQuantity < 0) {
    throw new Error(`The subscriptionConstraintMinQuantity field must be a positive number. Received: ${subscriptionConstraintMinQuantity}`);
  }

  return subscriptionConstraintMinQuantity;
}

export function validateSubscriptionConstraintMaxQuantity(subscriptionConstraintMaxQuantity: number | undefined, minQuantity: number): number {
  subscriptionConstraintMaxQuantity ??= unlimitedValue;

  if (typeof subscriptionConstraintMaxQuantity !== 'number') {
    throw new TypeError(`The subscriptionConstraintMaxQuantity field must be a number. Received: ${subscriptionConstraintMaxQuantity}`);
  }

  if (subscriptionConstraintMaxQuantity < minQuantity) {
    throw new Error(`The subscriptionConstraintMaxQuantity field must be greater than or equal to the min quantity. Received: ${subscriptionConstraintMaxQuantity}`);
  }

  return subscriptionConstraintMaxQuantity;
}

export function validateSubscriptionConstraintQuantityStep(subscriptionConstraintQuantityStep: number | undefined, minQuantity: number): number {
  subscriptionConstraintQuantityStep ??= 1;

  if (typeof subscriptionConstraintQuantityStep !== 'number') {
    throw new TypeError(`The subscriptionConstraintQuantityStep field must be a number. Received: ${subscriptionConstraintQuantityStep}`);
  }

  if (!(minQuantity % subscriptionConstraintQuantityStep === 0)) {
    throw new Error(`The subscriptionConstraintQuantityStep field must be a divisor of, at least, the min quantity. Received: ${subscriptionConstraintQuantityStep}`);
  }

  return subscriptionConstraintQuantityStep;
}

export function validateCustom(custom: { [key: string]: any } | undefined): { [key: string]: any } {
  custom ??= {};
  
  if (typeof custom !== 'object') {
    throw new TypeError(
      `The custom field must be an object of type {[key: string]: any}`
    );
  }

  return custom;
}