import '@testing-library/jest-dom';
import {
  hasUnsubscribeTag,
  validateTagsCore,
  getTagMapValue,
  getLoyaltyTagsMapValue,
  getForwardedMapValues,
  preprocessHtml,
  validateIfTagClosed,
  validateTags,
  skipTags,
  checkIfSupportedTag,
  transformInjectedTags,
} from '../tagValidations';
import { containsBase64Images } from '../content';

describe('hasUnsubscribeTag', () => {
  it('should return false when content is not a string', () => {
    expect(hasUnsubscribeTag(null)).toBe(false);
    expect(hasUnsubscribeTag(undefined)).toBe(false);
    expect(hasUnsubscribeTag(123)).toBe(false);
    expect(hasUnsubscribeTag({})).toBe(false);
  });

  it('should return false when content has no unsubscribe tag', () => {
    expect(hasUnsubscribeTag('Hello world')).toBe(false);
    expect(hasUnsubscribeTag('{{ name }}')).toBe(false);
  });

  it('should return true when content has {{ unsubscribe }}', () => {
    expect(hasUnsubscribeTag('Click {{ unsubscribe }} here')).toBe(true);
    expect(hasUnsubscribeTag('{{ unsubscribe }}')).toBe(true);
  });
});

describe('validateTagsCore', () => {
  it('should include isContentEmpty: false when includeIsContentEmpty is true', () => {
    const result = validateTagsCore({
      contentForBraceCheck: '{{a}}',
      contentForUnsubscribeScan: '{{a}}',
      tags: null,
      currentModule: 'default',
      isFullMode: true,
      includeIsContentEmpty: true,
    });
    expect(result.isContentEmpty).toBe(false);
    expect(result.valid).toBe(true);
  });

  it('should use initialMissingTags when provided', () => {
    const result = validateTagsCore({
      contentForBraceCheck: '{{a}}',
      contentForUnsubscribeScan: '{{a}}',
      tags: [{ definition: { supportedModules: [], value: 'x' } }],
      currentModule: 'default',
      isFullMode: false,
      initialMissingTags: ['requiredTag'],
    });
    expect(result.missingTags).toEqual(['requiredTag']);
    expect(result.valid).toBe(false);
  });

  it('should use custom skipTagsFn when provided', () => {
    const customSkip = jest.fn(() => true);
    const result = validateTagsCore({
      contentForBraceCheck: '{{ unsubscribe }}',
      contentForUnsubscribeScan: '{{ unsubscribe }}',
      tags: [
        {
          definition: {
            supportedModules: [{ context: 'DEFAULT', mandatory: true }],
            value: 'unsubscribe',
          },
        },
      ],
      currentModule: 'DEFAULT',
      isFullMode: false,
      skipTagsFn: customSkip,
    });
    expect(customSkip).toHaveBeenCalled();
    expect(result.valid).toBe(true);
  });
});

describe('checkIfSupportedTag', () => {
  it('should return false for empty or no injected tags', () => {
    expect(checkIfSupportedTag('someTag', {})).toBe(false);
    expect(checkIfSupportedTag('someTag', undefined)).toBe(false);
  });

  it('should return true when tag matches a top-level key', () => {
    const injectedTags = { name: { name: 'Name' }, unsubscribe: { name: 'Unsubscribe' } };
    expect(checkIfSupportedTag('name', injectedTags)).toBe(true);
    expect(checkIfSupportedTag('unsubscribe', injectedTags)).toBe(true);
  });

  it('should return false when tag does not match any key', () => {
    const injectedTags = { name: { name: 'Name' } };
    expect(checkIfSupportedTag('other', injectedTags)).toBe(false);
  });

  it('should return true when tag matches a nested subtag', () => {
    const injectedTags = {
      customer: {
        name: 'Customer',
        subtags: {
          first_name: { name: 'First Name' },
          last_name: { name: 'Last Name' },
        },
      },
    };
    expect(checkIfSupportedTag('first_name', injectedTags)).toBe(true);
    expect(checkIfSupportedTag('last_name', injectedTags)).toBe(true);
  });

  it('should return false when tag is in neither top-level nor subtags', () => {
    const injectedTags = {
      customer: {
        name: 'Customer',
        subtags: { first_name: { name: 'First Name' } },
      },
    };
    expect(checkIfSupportedTag('unknown', injectedTags)).toBe(false);
  });
});

describe('transformInjectedTags', () => {
  it('should add tag-header and normalize subtags when key contains "subtags"', () => {
    const tags = [
      {
        name: 'Customer',
        subtags: {
          first_name: { name: 'First Name' },
        },
      },
    ];
    const result = transformInjectedTags(tags);
    expect(result).toBe(tags);
    expect(tags[0]['tag-header']).toBe(true);
    expect(tags[0].subtags).toEqual({ first_name: { name: 'First Name' } });
  });

  it('should recursively transform nested subtags', () => {
    const tags = [
      {
        name: 'Parent',
        subtags: {
          child: {
            name: 'Child',
            subtags: { grandchild: { name: 'Grandchild' } },
          },
        },
      },
    ];
    transformInjectedTags(tags);
    expect(tags[0].subtags.child.subtags).toEqual({ grandchild: { name: 'Grandchild' } });
  });

  it('should return tags unchanged when no subtag keys exist', () => {
    const tags = [{ name: 'Simple', desc: 'No subtags' }];
    const result = transformInjectedTags(tags);
    expect(result).toBe(tags);
    expect(tags[0]).toEqual({ name: 'Simple', desc: 'No subtags' });
  });
});

describe("check if curly brackets are balanced", () => {
    it("test for balanced curly brackets", () => {
        let value = "hello {{optout"
        let res = validateIfTagClosed(value);
        expect(res).toEqual(false);
        value += "}}"
        let result = validateIfTagClosed(value);
        expect(result).toEqual(true);
        // no braces or empty string: match returns null, l1/l2/l3 undefined -> true
        expect(validateIfTagClosed("")).toEqual(true);
        expect(validateIfTagClosed("plain text no braces")).toEqual(true);
        //valid cases
        expect(validateIfTagClosed("{{{Hello}}}")).toEqual(true);
        expect(validateIfTagClosed("{{{Hello}}")).toEqual(true);
        expect(validateIfTagClosed("{{Hello}}}")).toEqual(true);

        //invalid cases
        expect(validateIfTagClosed("{{Hello}")).toEqual(false);
        expect(validateIfTagClosed("{Hello}}")).toEqual(false);
        expect(validateIfTagClosed("{{{Hello}")).toEqual(false);
        expect(validateIfTagClosed("{Hello}}}")).toEqual(false);
        expect(validateIfTagClosed("{{{{Hello}}}}")).toEqual(false);
        expect(validateIfTagClosed("{{{{{Hello}}}}}")).toEqual(false);
        expect(validateIfTagClosed("{{{{Hello}}")).toEqual(false);
        expect(validateIfTagClosed("{{{{Hello}}World}}")).toEqual(false);
        expect(validateIfTagClosed("{{Hello{{World}}")).toEqual(false);
    })
})

describe("validateTags", () => {
  const tagsParam = [
    {
      definition: {
        supportedModules: [{ context: "DEFAULT", mandatory: true }],
        value: "tag1",
      },
    },
    {
      definition: {
        supportedModules: [{ context: "DEFAULT", mandatory: true }],
        value: "tag2",
      },
    },
    {
      definition: {
        supportedModules: [{ context: "DEFAULT", mandatory: true }],
        value: "tag3",
      },
    },
  ];
  it("should return valid response when all tags are present", () => {
    const content = "Hello {{tag1}}, {{tag2}}, {{tag3}} {{entryTrigger.lifetimePurchases}}";

    const location = { query: { module: "DEFAULT" } };
    const tagModule = null;

    const result = validateTags({
      content,
      tagsParam,
      location,
      tagModule,
    });

    expect(result.valid).toEqual(true);
    expect(result.missingTags).toEqual([]);
    expect(result.isBraceError).toEqual(false);
  });

  it("should return valid response when content has balanced braces and all tags present", () => {
    const content = "Hello {{tag1}}, {{tag2}}, {{tag3}}";
    const location = { query: { module: "DEFAULT" } };
    const tagModule = null;

    const result = validateTags({
      content,
      tagsParam,
      location,
      tagModule,
    });

    expect(result.valid).toEqual(true);
    expect(result.missingTags).toEqual([]);
    expect(result.isBraceError).toEqual(false);
  });

  it("should return valid response when content has balanced braces with missingEventContextTags and partial tagsParam", () => {
    const content = "Hello {{tag1}}, {{tag2}}, {{tag3}} {{missingEventContextTags}}";
    const tagsParamLocal = [
      {
        definition: {
          supportedModules: [{ context: "DEFAULT", mandatory: true }],
          value: "tag1",
        },
      },
      {
        definition: {
          supportedModules: [{ context: "DEFAULT", mandatory: true }],
          value: "tag2",
        },
      },
    ];
    const location = { query: { module: "DEFAULT" } };
    const tagModule = null;

    const result = validateTags({
      content,
      tagsParam: tagsParamLocal,
      location,
      tagModule,
    });

    expect(result.valid).toEqual(true);
    expect(result.missingTags).toEqual([]);
    expect(result.isBraceError).toEqual(false);
  });

  it("should return invalid response when there is an unbalanced bracket error", () => {
    const content = "Hello {{tag1}, {{tag2}}, {{tag3}}";
    const tagsParamLocal = [
      {
        definition: {
          supportedModules: [{ context: "DEFAULT", mandatory: true }],
          value: "tag1",
        },
      },
      {
        definition: {
          supportedModules: [{ context: "DEFAULT", mandatory: true }],
          value: "tag2",
        },
      },
      {
        definition: {
          supportedModules: [{ context: "DEFAULT", mandatory: true }],
          value: "tag3",
        },
      },
    ];
    const location = { query: { module: "DEFAULT" } };
    const tagModule = null;

    const result = validateTags({
      content,
      tagsParam: tagsParamLocal,
      location,
      tagModule,
    });

    expect(result.valid).toEqual(false);
    expect(result.missingTags).toEqual([]);
    expect(result.isBraceError).toEqual(true);
  });

  it("should require unsubscribe when mandatory and missing, and accept skipped unsubscribe variant", () => {
    const tagsParamUnsubscribe = [
      {
        definition: {
          supportedModules: [{ context: "DEFAULT", mandatory: true }],
          value: "unsubscribe",
        },
      },
    ];
    const location = { query: { module: "DEFAULT" } };
    const tagModule = null;

    const contentMissing = "Hello world";
    const resultMissing = validateTags({
      content: contentMissing,
      tagsParam: tagsParamUnsubscribe,
      location,
      tagModule,
    });
    expect(resultMissing.missingTags).toContain("unsubscribe");
    expect(resultMissing.valid).toBe(false);

    const contentWithSkippedUnsubscribe = "Hello {{unsubscribe(#123456)}}";
    const resultSkipped = validateTags({
      content: contentWithSkippedUnsubscribe,
      tagsParam: tagsParamUnsubscribe,
      location,
      tagModule,
    });
    expect(resultSkipped.missingTags).not.toContain("unsubscribe");
    expect(resultSkipped.valid).toBe(true);

    const contentWithWhitespaceUnsubscribe = "Hello {{ unsubscribe }}";
    const resultWhitespace = validateTags({
      content: contentWithWhitespaceUnsubscribe,
      tagsParam: tagsParamUnsubscribe,
      location,
      tagModule,
    });
    expect(resultWhitespace.missingTags).not.toContain("unsubscribe");
    expect(resultWhitespace.valid).toBe(true);
    expect(resultWhitespace.unsupportedTags ?? []).toEqual([]);
  });

  it('should treat tags from waitEventContextTags as supported', () => {
    const content = 'Hello {{waitEvent.orderId}}';
    const tagsParam = [];
    const injectedTagsParams = [];
    const location = { query: { module: 'DEFAULT' } };
    const tagModule = null;

    const result = validateTags({
      content,
      tagsParam,
      injectedTagsParams,
      location,
      tagModule,
    });

    expect(result.valid).toEqual(true);
    expect(result.missingTags).toEqual([]);
    expect(result.isBraceError).toEqual(false);
  });
});

describe('validateTags wrapper (v2 consumers)', () => {
  const tagsWithUnsubscribe = [
    {
      definition: {
        supportedModules: [{ context: 'DEFAULT', mandatory: true }],
        value: 'unsubscribe',
      },
    },
  ];
  const tagsOutboundUnsubscribe = [
    {
      definition: {
        supportedModules: [{ context: 'outbound', mandatory: true }],
        value: 'unsubscribe',
      },
    },
  ];
  const tagsDefaultLowercase = [
    {
      definition: {
        supportedModules: [{ context: 'default', mandatory: true }],
        value: 'unsubscribe',
      },
    },
  ];

  describe('module selection (location.query.module vs tagModule vs DEFAULT)', () => {
    it('uses location.query.module when set and tagModule not provided', () => {
      const content = 'Hello world';
      const result = validateTags({
        content,
        tagsParam: tagsWithUnsubscribe,
        location: { query: { module: 'DEFAULT' } },
        tagModule: null,
      });
      expect(result.missingTags).toContain('unsubscribe');
      expect(result.valid).toBe(false);
    });

    it('uses tagModule override when provided (overrides location.query.module)', () => {
      const content = 'Hello world';
      const resultWithOutbound = validateTags({
        content,
        tagsParam: tagsOutboundUnsubscribe,
        location: { query: { module: 'DEFAULT' } },
        tagModule: 'outbound',
      });
      expect(resultWithOutbound.missingTags).toContain('unsubscribe');
      expect(resultWithOutbound.valid).toBe(false);

      const resultWithDefault = validateTags({
        content,
        tagsParam: tagsWithUnsubscribe,
        location: { query: { module: 'outbound' } },
        tagModule: 'DEFAULT',
      });
      expect(resultWithDefault.missingTags).toContain('unsubscribe');
      expect(resultWithDefault.valid).toBe(false);
    });

    it('uses DEFAULT (lowercase) when location is undefined', () => {
      const content = 'Hello world';
      const result = validateTags({
        content,
        tagsParam: tagsDefaultLowercase,
        location: undefined,
        tagModule: null,
      });
      expect(result.missingTags).toContain('unsubscribe');
      expect(result.valid).toBe(false);
    });

    it('uses DEFAULT when location.query is undefined', () => {
      const content = 'Hello world';
      const result = validateTags({
        content,
        tagsParam: tagsDefaultLowercase,
        location: {},
        tagModule: null,
      });
      expect(result.missingTags).toContain('unsubscribe');
      expect(result.valid).toBe(false);
    });

    it('uses DEFAULT when location.query.module is falsy', () => {
      const content = 'Hello world';
      const result = validateTags({
        content,
        tagsParam: tagsDefaultLowercase,
        location: { query: { module: '' } },
        tagModule: null,
      });
      expect(result.missingTags).toContain('unsubscribe');
      expect(result.valid).toBe(false);
    });
  });

  describe('content passed to contentForBraceCheck and contentForUnsubscribeScan', () => {
    it('uses same content for brace check (isBraceError when unbalanced)', () => {
      const content = 'Hello {{tag1}, {{tag2}}';
      const result = validateTags({
        content,
        tagsParam: [],
        location: { query: { module: 'DEFAULT' } },
      });
      expect(result.isBraceError).toBe(true);
      expect(result.valid).toBe(false);
    });

    it('uses same content for unsubscribe scan (missing unsubscribe when required)', () => {
      const content = 'Hello {{other}}';
      const result = validateTags({
        content,
        tagsParam: tagsWithUnsubscribe,
        location: { query: { module: 'DEFAULT' } },
      });
      expect(result.missingTags).toContain('unsubscribe');
      expect(result.valid).toBe(false);
    });

    it('content with {{ unsubscribe }} satisfies unsubscribe requirement', () => {
      const content = 'Hello {{ unsubscribe }}';
      const result = validateTags({
        content,
        tagsParam: tagsWithUnsubscribe,
        location: { query: { module: 'DEFAULT' } },
      });
      expect(result.missingTags).not.toContain('unsubscribe');
      expect(result.valid).toBe(true);
    });
  });

  describe('isFullMode', () => {
    it('when true skips unsubscribe check (no missingTags for mandatory unsubscribe)', () => {
      const content = 'Hello world';
      const result = validateTags({
        content,
        tagsParam: tagsWithUnsubscribe,
        location: { query: { module: 'DEFAULT' } },
        isFullMode: true,
      });
      expect(result.missingTags).toEqual([]);
      expect(result.valid).toBe(true);
    });

    it('when true still runs brace check', () => {
      const content = 'Hello {{tag1}';
      const result = validateTags({
        content,
        tagsParam: tagsWithUnsubscribe,
        location: { query: { module: 'DEFAULT' } },
        isFullMode: true,
      });
      expect(result.isBraceError).toBe(true);
      expect(result.valid).toBe(false);
    });
  });

  describe('tagsParam null or empty', () => {
    it('when null: only brace check runs, no missing-tag logic', () => {
      const content = 'Hello {{a}}';
      const result = validateTags({
        content,
        tagsParam: null,
        location: { query: { module: 'DEFAULT' } },
      });
      expect(result.missingTags).toEqual([]);
      expect(result.isBraceError).toBe(false);
      expect(result.valid).toBe(true);
    });

    it('when empty array: only brace check runs', () => {
      const content = 'Hello {{a}}';
      const result = validateTags({
        content,
        tagsParam: [],
        location: { query: { module: 'DEFAULT' } },
      });
      expect(result.missingTags).toEqual([]);
      expect(result.valid).toBe(true);
    });

    it('when null with unbalanced braces: returns isBraceError', () => {
      const content = 'Hello {{a}';
      const result = validateTags({
        content,
        tagsParam: null,
        location: { query: { module: 'DEFAULT' } },
      });
      expect(result.isBraceError).toBe(true);
      expect(result.valid).toBe(false);
      expect(result.missingTags).toEqual([]);
    });
  });

  describe('v2 consumer call patterns', () => {
    it('Whatsapp-style: content, tagsParam, location, tagModule (getDefaultTags), isFullMode', () => {
      const content = 'Hello {{ unsubscribe }}';
      const result = validateTags({
        content: content,
        tagsParam: tagsWithUnsubscribe,
        location: { query: { module: 'DEFAULT' } },
        tagModule: 'DEFAULT',
        isFullMode: false,
      });
      expect(result.valid).toBe(true);
      expect(result.isBraceError).toBe(false);
    });

    it('Zalo / Rcs / MobilePushNew / EmailWrapper: same pattern as Whatsapp', () => {
      const result = validateTags({
        content: 'Hi {{ unsubscribe }}',
        tagsParam: tagsWithUnsubscribe,
        location: { query: { module: 'DEFAULT' } },
        tagModule: 'DEFAULT',
        isFullMode: false,
      });
      expect(result.valid).toBe(true);
    });

    it('Line / Viber: tagModule "outbound"', () => {
      const contentMissing = 'Hello';
      const resultMissing = validateTags({
        content: contentMissing,
        tagsParam: tagsOutboundUnsubscribe,
        location: { query: { module: 'inbound' } },
        tagModule: 'outbound',
        isFullMode: false,
      });
      expect(resultMissing.missingTags).toContain('unsubscribe');
      expect(resultMissing.valid).toBe(false);

      const resultOk = validateTags({
        content: 'Hello {{ unsubscribe }}',
        tagsParam: tagsOutboundUnsubscribe,
        location: {},
        tagModule: 'outbound',
        isFullMode: false,
      });
      expect(resultOk.valid).toBe(true);
    });

    it('WebPush: validationConfig spread (content + tagsParam, location, tagModule, isFullMode)', () => {
      const validationConfig = {
        tagsParam: tagsWithUnsubscribe,
        location: { query: { module: 'DEFAULT' } },
        tagModule: 'DEFAULT',
      };
      const result = validateTags({
        content: 'Hello {{ unsubscribe }}',
        ...validationConfig,
        isFullMode: false,
      });
      expect(result.valid).toBe(true);
      expect(result.isBraceError).toBe(false);
    });

    it('WebPush with isFullMode: only brace check', () => {
      const result = validateTags({
        content: 'Hello world',
        tagsParam: tagsWithUnsubscribe,
        location: { query: { module: 'DEFAULT' } },
        tagModule: 'DEFAULT',
        isFullMode: true,
      });
      expect(result.valid).toBe(true);
      expect(result.missingTags).toEqual([]);
    });
  });
});

describe('preprocessHtml', () => {
  it('should replace HTML entities with their corresponding characters', () => {
    const input = "This &amp; that &lt; those &gt; these &#39;quotes&#39;";
    const expectedOutput = "This & that < those > these 'quotes'";
    expect(preprocessHtml(input)).toEqual(expectedOutput);
  });

  it('should replace &quot; with double quotes', () => {
    const input = 'She said, &quot;Hello, World!&quot;';
    const expectedOutput = 'She said, "Hello, World!"';
    expect(preprocessHtml(input)).toEqual(expectedOutput);
  });

  it('should remove newline characters', () => {
    const input = "Line one\nLine two\nLine three";
    const expectedOutput = "Line oneLine twoLine three";
    expect(preprocessHtml(input)).toEqual(expectedOutput);
  });

  it('should handle multiple replacements and newlines in the same string', () => {
    const input = "&lt;div&gt;\nContent &amp; More Content\n&lt;/div&gt;";
    const expectedOutput = "<div>Content & More Content</div>";
    expect(preprocessHtml(input)).toEqual(expectedOutput);
  });

  it('should return the same string if there are no matching patterns', () => {
    const input = "No special characters here!";
    const expectedOutput = "No special characters here!";
    expect(preprocessHtml(input)).toEqual(expectedOutput);
  });

  it('should handle an empty string input', () => {
    const input = "";
    const expectedOutput = "";
    expect(preprocessHtml(input)).toEqual(expectedOutput);
  });

  it('should handle strings with only newline characters', () => {
    const input = "\n\n\n";
    const expectedOutput = "";
    expect(preprocessHtml(input)).toEqual(expectedOutput);
  });

  it('should not  affect similar but non-matching sequences', () => {
    const input = "This is &amplt; not an entity.";
    const expectedOutput = "This is &amplt; not an entity.";
    expect(preprocessHtml(input)).toEqual(expectedOutput);
  });
  it('Should handle style tags with %}', () => {
    const input = "<style {line-height:0;font-size:75%} >sup{line-height:0;font-size:75%}</style> ";
    const expectedOutput = "<style {line-height:0;font-size:75%} >sup{line-height:0;font-size:75% }</style> ";
    expect(preprocessHtml(input)).toEqual(expectedOutput);
  });


});


describe("getTagMapValue", () => {
  it("should return an empty object when no object is provided", () => {
    const result = getTagMapValue();
    expect(result).toEqual({});
  });

  it("should return the tag map value when an object is provided", () => {
    const object = {
      "key 1": {
        name: "23",
        subtags: {
          name: "233",
        },
      },
      "key 2": {
        name: "24",
        subtags: {
          tagName: "234",
        },
      },
    };
    const result = getTagMapValue(object);
    expect(result).toEqual({ name: "233", tagName: "234", });
  });
  it("should return the tag map value when an object is provided but subtag is not present in one of the obj", () => {
    const object = {
      "key 1": {
        name: "23",
        subtags: {
          name: "233",
        },
      },
      "key 2": {
        name: "24",
      },
    };
    const result = getTagMapValue(object);
    expect(result).toEqual({ name: "233" });
  });
});

describe("getLoyaltyTagsMapValue", () => {
  it("should return an empty object when no object is provided", () => {
    const result = getLoyaltyTagsMapValue();
    expect(result).toEqual({});
  });

  it("should return an empty object when an empty object is provided", () => {
    const result = getLoyaltyTagsMapValue({});
    expect(result).toEqual({});
  });

  it("should keep the tag itself when no subtags are present", () => {
    const object = {
      customer: {
        name: "Customer",
        desc: "Customer Description",
      },
    };
    const result = getLoyaltyTagsMapValue(object);
    expect(result).toEqual({
      customer: {
        name: "Customer",
        desc: "Customer Description",
      },
    });
  });

  it("should keep the tag itself when subtags is undefined", () => {
    const object = {
      customer: {
        name: "Customer",
        desc: "Customer Description",
        subtags: undefined,
      },
    };
    const result = getLoyaltyTagsMapValue(object);
    expect(result).toEqual({
      customer: {
        name: "Customer",
        desc: "Customer Description",
        subtags: undefined,
      },
    });
  });

  it("should keep the tag itself when subtags is null", () => {
    const object = {
      customer: {
        name: "Customer",
        desc: "Customer Description",
        subtags: null,
      },
    };
    const result = getLoyaltyTagsMapValue(object);
    expect(result).toEqual({
      customer: {
        name: "Customer",
        desc: "Customer Description",
        subtags: null,
      },
    });
  });

  it("should keep the tag itself when subtags is an empty object", () => {
    const object = {
      customer: {
        name: "Customer",
        desc: "Customer Description",
        subtags: {},
      },
    };
    const result = getLoyaltyTagsMapValue(object);
    expect(result).toEqual({
      customer: {
        name: "Customer",
        desc: "Customer Description",
        subtags: {},
      },
    });
  });

  it("should merge subtags when subtags exist and have items", () => {
    const object = {
      customer: {
        name: "Customer",
        desc: "Customer Description",
        subtags: {
          cumulative_points_currency: {
            name: "Lifetime Points (in ₹)",
            desc: "Lifetime Points in ₹ Description",
          },
          mobile_number: {
            name: "Mobile Number",
            desc: "Mobile Number Description",
          },
        },
      },
    };
    const result = getLoyaltyTagsMapValue(object);
    expect(result).toEqual({
      cumulative_points_currency: {
        name: "Lifetime Points (in ₹)",
        desc: "Lifetime Points in ₹ Description",
      },
      mobile_number: {
        name: "Mobile Number",
        desc: "Mobile Number Description",
      },
    });
  });

  it("should handle mixed scenario with some tags having subtags and some without", () => {
    const object = {
      customer: {
        name: "Customer",
        desc: "Customer Description",
        subtags: {
          cumulative_points_currency: {
            name: "Lifetime Points (in ₹)",
            desc: "Lifetime Points in ₹ Description",
          },
        },
      },
      points_on_event: {
        name: "Points on event",
        desc: "Points on event Description",
      },
      store: {
        name: "Store",
        desc: "Store Description",
        subtags: {
          store_name: {
            name: "Store Name",
            desc: "Store Name Description",
          },
        },
      },
    };
    const result = getLoyaltyTagsMapValue(object);
    expect(result).toEqual({
      cumulative_points_currency: {
        name: "Lifetime Points (in ₹)",
        desc: "Lifetime Points in ₹ Description",
      },
      points_on_event: {
        name: "Points on event",
        desc: "Points on event Description",
      },
      store_name: {
        name: "Store Name",
        desc: "Store Name Description",
      },
    });
  });

  it("should merge multiple subtags from multiple parent tags", () => {
    const object = {
      customer: {
        name: "Customer",
        subtags: {
          first_name: { name: "First Name" },
          last_name: { name: "Last Name" },
        },
      },
      store: {
        name: "Store",
        subtags: {
          store_name: { name: "Store Name" },
          store_address: { name: "Store Address" },
        },
      },
    };
    const result = getLoyaltyTagsMapValue(object);
    expect(result).toEqual({
      first_name: { name: "First Name" },
      last_name: { name: "Last Name" },
      store_name: { name: "Store Name" },
      store_address: { name: "Store Address" },
    });
  });

  it("should handle tags where current value is null", () => {
    const object = {
      customer: {
        name: "Customer",
        subtags: {
          mobile_number: { name: "Mobile Number" },
        },
      },
      invalidTag: null,
    };
    const result = getLoyaltyTagsMapValue(object);
    expect(result).toEqual({
      mobile_number: { name: "Mobile Number" },
      invalidTag: null,
    });
  });

  it("should handle tags where current value is undefined", () => {
    const object = {
      customer: {
        name: "Customer",
        subtags: {
          mobile_number: { name: "Mobile Number" },
        },
      },
      invalidTag: undefined,
    };
    const result = getLoyaltyTagsMapValue(object);
    expect(result).toEqual({
      mobile_number: { name: "Mobile Number" },
      invalidTag: undefined,
    });
  });

  it("should handle a single tag with multiple nested subtags", () => {
    const object = {
      leaderboard: {
        name: "Leaderboard",
        desc: "Leaderboard Description",
        subtags: {
          "person.userId": {
            name: "User ID",
            desc: "User ID Description",
          },
          "person.rank": {
            name: "User Rank",
            desc: "User Rank Description",
          },
          "person.points": {
            name: "User Points",
            desc: "User Points Description",
          },
        },
      },
    };
    const result = getLoyaltyTagsMapValue(object);
    expect(result).toEqual({
      "person.userId": {
        name: "User ID",
        desc: "User ID Description",
      },
      "person.rank": {
        name: "User Rank",
        desc: "User Rank Description",
      },
      "person.points": {
        name: "User Points",
        desc: "User Points Description",
      },
    });
  });

  it("should handle all tags without subtags", () => {
    const object = {
      unsubscribe: {
        name: "Unsubscribe",
        desc: "Unsubscribe Description",
      },
      optout: {
        name: "Opt Out",
        desc: "Opt Out Description",
      },
    };
    const result = getLoyaltyTagsMapValue(object);
    expect(result).toEqual({
      unsubscribe: {
        name: "Unsubscribe",
        desc: "Unsubscribe Description",
      },
      optout: {
        name: "Opt Out",
        desc: "Opt Out Description",
      },
    });
  });

  it("should handle all tags with subtags", () => {
    const object = {
      customer: {
        name: "Customer",
        subtags: {
          first_name: { name: "First Name" },
        },
      },
      store: {
        name: "Store",
        subtags: {
          store_name: { name: "Store Name" },
        },
      },
    };
    const result = getLoyaltyTagsMapValue(object);
    expect(result).toEqual({
      first_name: { name: "First Name" },
      store_name: { name: "Store Name" },
    });
  });

  it("should not mutate the original input object", () => {
    const object = {
      customer: {
        name: "Customer",
        desc: "Customer Description",
        subtags: {
          cumulative_points_currency: {
            name: "Lifetime Points (in ₹)",
            desc: "Lifetime Points in ₹ Description",
          },
        },
      },
    };
    const inputCopy = JSON.parse(JSON.stringify(object));
    
    getLoyaltyTagsMapValue(object);
    
    expect(object).toEqual(inputCopy);
  });

  it("should handle tags with empty string keys", () => {
    const object = {
      "": {
        name: "Empty Key",
        desc: "Empty Key Description",
      },
    };
    const result = getLoyaltyTagsMapValue(object);
    expect(result).toEqual({
      "": {
        name: "Empty Key",
        desc: "Empty Key Description",
      },
    });
  });

  it("should handle complex nested structure with subtags that have nested properties", () => {
    const object = {
      customer: {
        name: "Customer",
        desc: "Customer Description",
        subtags: {
          child: {
            name: "Child",
            desc: "Child Description",
            // This nested subtag property is included in the merge
            nestedProperty: {
              key: "value",
            },
          },
        },
      },
    };
    const result = getLoyaltyTagsMapValue(object);
    expect(result).toEqual({
      child: {
        name: "Child",
        desc: "Child Description",
        nestedProperty: {
          key: "value",
        },
      },
    });
  });
});

describe("skipTags", () => {
  it("should return false for event context tags", () => {
    const tag = "entryTrigger.uniqueTagPrefix(raindrops_match)";
    const result = skipTags(tag);
    expect(result).toEqual(false);
  });

  it("should return true for tags matching regex patterns", () => {
    const tag = "voucher(12345)";
    const result = skipTags(tag);
    expect(result).toEqual(true);
  });

  it("should return true for referral unique code tags with userid", () => {
    const tag = "referral_unique_code_C6SOE_userid";
    const result = skipTags(tag);
    expect(result).toEqual(true);
  });

  it("should return true for referral unique url tags with userid", () => {
    const tag = "referral_unique_url_C6SOE_userid";
    const result = skipTags(tag);
    expect(result).toEqual(true);
  });

  it("should return true for referral unique code tags with different codes", () => {
    const tag = "referral_unique_code_ABC123_userid";
    const result = skipTags(tag);
    expect(result).toEqual(true);
  });

  it("should return true for referral unique url tags with different codes", () => {
    const tag = "referral_unique_url_ABC123_userid";
    const result = skipTags(tag);
    expect(result).toEqual(true);
  });

  it("should return true for referral unique code tags with alphanumeric codes", () => {
    const tag = "referral_unique_code_XYZ789_userid";
    const result = skipTags(tag);
    expect(result).toEqual(true);
  });

  it("should return true for referral unique url tags with alphanumeric codes", () => {
    const tag = "referral_unique_url_XYZ789_userid";
    const result = skipTags(tag);
    expect(result).toEqual(true);
  });

  it("should return false for tags that don't match referral pattern", () => {
    const tag = "referral_code_userid";
    const result = skipTags(tag);
    expect(result).toEqual(false);
  });

  it("should return false for tags with referral but missing unique_code or unique_url", () => {
    const tag = "referral_C6SOE_userid";
    const result = skipTags(tag);
    expect(result).toEqual(false);
  });

  it("should return false for tags with referral_unique_code but missing userid", () => {
    const tag = "referral_unique_code_C6SOE";
    const result = skipTags(tag);
    expect(result).toEqual(false);
  });

  it("should return false for tags with referral_unique_url but missing userid", () => {
    const tag = "referral_unique_url_C6SOE";
    const result = skipTags(tag);
    expect(result).toEqual(false);
  });

  it("should return true for referral_unique_code tags with numeric codes like 632L7", () => {
    const tag = "referral_unique_code_632L7_userid";
    const result = skipTags(tag);
    expect(result).toEqual(true);
  });

  it("should return true for referral_unique_url tags with numeric codes like 632L7", () => {
    const tag = "referral_unique_url_632L7_userid";
    const result = skipTags(tag);
    expect(result).toEqual(true);
  });

  it("should return true for referral tags with various token formats", () => {
    const tags = [
      "referral_unique_code_123_userid",
      "referral_unique_code_ABC_userid",
      "referral_unique_code_123ABC_userid",
      "referral_unique_url_456_userid",
      "referral_unique_url_DEF_userid",
      "referral_unique_url_456DEF_userid",
    ];
    tags.forEach((tag) => {
      expect(skipTags(tag)).toEqual(true);
    });
  });
});


describe('getForwardedMapValues', () => {
  test('should return an empty object when input is empty', () => {
    const input = {};
    const expected = {};
    expect(getForwardedMapValues(input)).toEqual(expected);
  });

  test('should return empty object when called with no argument (default param)', () => {
    expect(getForwardedMapValues()).toEqual({});
  });

  test('should correctly process objects with subtags', () => {
    const input = {
      customer: {
        name: 'Customer',
        desc: 'Customer Description',
        subtags: {
          cumulative_points_currency: {
            name: 'Lifetime Points (in ₹)',
            desc: 'Lifetime Points in ₹ Description',
          },
          mobile_number: {
            name: 'Mobile Number',
            desc: 'Mobile Number Description',
          },
        },
      },
      store: {
        name: 'Store',
        desc: 'Store Description',
        subtags: {
          store_name: {
            name: 'Store Name',
            desc: 'Store Name Description',
          },
          store_address: {
            name: 'Store Address',
            desc: 'Store Address Description',
          },
        },
      },
    };

    const expected = {
      customer: {
        name: 'Customer',
        desc: 'Customer Description',
      },
      cumulative_points_currency: {
        name: 'Lifetime Points (in ₹)',
        desc: 'Lifetime Points in ₹ Description',
      },
      mobile_number: {
        name: 'Mobile Number',
        desc: 'Mobile Number Description',
      },
      store: {
        name: 'Store',
        desc: 'Store Description',
      },
      store_name: {
        name: 'Store Name',
        desc: 'Store Name Description',
      },
      store_address: {
        name: 'Store Address',
        desc: 'Store Address Description',
      },
    };

    expect(getForwardedMapValues(input)).toEqual(expected);
  });

  test('should correctly process objects without subtags', () => {
    const input = {
      points_on_event: {
        name: 'Points on event',
        desc: 'Points on event Description',
      },
      unsubscribe: {
        name: 'Unsubscribe',
        desc: 'Unsubscribe Description',
      },
    };

    const expected = {
      points_on_event: {
        name: 'Points on event',
        desc: 'Points on event Description',
      },
      unsubscribe: {
        name: 'Unsubscribe',
        desc: 'Unsubscribe Description',
      },
    };

    expect(getForwardedMapValues(input)).toEqual(expected);
  });

  test('should handle a mix of entries with and without subtags', () => {
    const input = {
      customer: {
        name: 'Customer',
        desc: 'Customer Description',
        subtags: {
          cumulative_points_currency: {
            name: 'Lifetime Points (in ₹)',
            desc: 'Lifetime Points in ₹ Description',
          },
        },
      },
      points_on_event: {
        name: 'Points on event',
        desc: 'Points on event Description',
      },
      store: {
        name: 'Store',
        desc: 'Store Description',
        subtags: {
          store_name: {
            name: 'Store Name',
            desc: 'Store Name Description',
          },
        },
      },
    };

    const expected = {
      customer: {
        name: 'Customer',
        desc: 'Customer Description',
      },
      cumulative_points_currency: {
        name: 'Lifetime Points (in ₹)',
        desc: 'Lifetime Points in ₹ Description',
      },
      points_on_event: {
        name: 'Points on event',
        desc: 'Points on event Description',
      },
      store: {
        name: 'Store',
        desc: 'Store Description',
      },
      store_name: {
        name: 'Store Name',
        desc: 'Store Name Description',
      },
    };

    expect(getForwardedMapValues(input)).toEqual(expected);
  });

  test('should ignore entries that are not objects', () => {
    const input = {
      valid_entry: {
        name: 'Valid Entry',
        desc: 'Valid Entry Description',
      },
      invalid_entry: 'This is a string, not an object',
      another_invalid_entry: null,
    };

    const expected = {
      valid_entry: {
        name: 'Valid Entry',
        desc: 'Valid Entry Description',
      },
    };

    expect(getForwardedMapValues(input)).toEqual(expected);
  });

  test('should handle entries missing name or desc', () => {
    const input = {
      incomplete_entry1: {
        name: 'Incomplete Entry 1',
        // desc is missing
        subtags: {
          subtag1: {
            name: 'Subtag 1',
            desc: 'Subtag 1 Description',
          },
        },
      },
      incomplete_entry2: {
        // name is missing
        desc: 'Incomplete Entry 2 Description',
      },
    };

    const expected = {
      incomplete_entry1: {
        name: 'Incomplete Entry 1',
        desc: undefined,
      },
      subtag1: {
        name: 'Subtag 1',
        desc: 'Subtag 1 Description',
      },
      incomplete_entry2: {
        name: undefined,
        desc: 'Incomplete Entry 2 Description',
      },
    };

    expect(getForwardedMapValues(input)).toEqual(expected);
  });

  test('should handle deeply nested subtags by only flattening one level', () => {
    const input = {
      parent: {
        name: 'Parent',
        desc: 'Parent Description',
        subtags: {
          child: {
            name: 'Child',
            desc: 'Child Description',
            subtags: {
              grandchild: {
                name: 'Grandchild',
                desc: 'Grandchild Description',
              },
            },
          },
        },
      },
    };

    const expected = {
      parent: {
        name: 'Parent',
        desc: 'Parent Description',
      },
      child: {
        name: 'Child',
        desc: 'Child Description',
        subtags: {
          grandchild: {
            name: 'Grandchild',
            desc: 'Grandchild Description',
          },
        },
      },
    };

    expect(getForwardedMapValues(input)).toEqual(expected);
  });


  test('should not mutate the original input object', () => {
    const input = {
      customer: {
        name: 'Customer',
        desc: 'Customer Description',
        subtags: {
          cumulative_points_currency: {
            name: 'Lifetime Points (in ₹)',
            desc: 'Lifetime Points in ₹ Description',
          },
        },
      },
    };

    const inputCopy = JSON.parse(JSON.stringify(input)); // Deep copy

    getForwardedMapValues(input);

    expect(input).toEqual(inputCopy);
  });
});

describe('containsBase64Images', () => {
  let mockCapNotification;
  let mockCallback;

  beforeEach(() => {
    mockCapNotification = {
      error: jest.fn()
    };
    mockCallback = jest.fn();
  });

  it('should return false for empty or null content', () => {
    expect(containsBase64Images({content: null})).toBe(false);
    expect(containsBase64Images({content: undefined})).toBe(false);
    expect(containsBase64Images({content: ''})).toBe(false);
    expect(containsBase64Images({content: '   '})).toBe(false);
  });

  it('should return false for content without images', () => {
    const content = '<div><p>Hello world</p></div>';
    expect(containsBase64Images({content})).toBe(false);
    expect(mockCapNotification.error).not.toHaveBeenCalled();
  });

  it('should return false for content with regular image URLs', () => {
    const content = '<img src="https://example.com/image.jpg" alt="test" />';
    expect(containsBase64Images({content, CapNotification: mockCapNotification})).toBe(false);
    expect(mockCapNotification.error).not.toHaveBeenCalled();
  });

  it('should return true for content with base64 images and trigger notification', () => {
    const content = '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==" alt="test" />';
    expect(containsBase64Images({content, CapNotification: mockCapNotification, callback: mockCallback})).toBe(true);
    
    expect(mockCapNotification.error).toHaveBeenCalledWith({
      key: 'base64Error',
      message: 'Base64 images are not allowed. Please upload your image to a gallery and use it, or add the image URL instead.',
      description: 'Base64 images are not allowed. Please upload your image to a gallery and use it, or add the image URL instead.',
    });
    expect(mockCallback).toHaveBeenCalled();
  });

  it('should return true for content with multiple base64 images', () => {
    const content = `
      <div>
        <img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD..." alt="image1" />
        <p>Some text</p>
        <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP//..." alt="image2" />
      </div>
    `;
    expect(containsBase64Images({content, CapNotification: mockCapNotification})).toBe(true);
    expect(mockCapNotification.error).toHaveBeenCalled();
  });

  it('should return false for content with data URLs that are not images', () => {
    const content = '<a href="data:text/plain;base64,SGVsbG8gV29ybGQ=">Download</a>';
    expect(containsBase64Images({content, CapNotification: mockCapNotification})).toBe(false);
    expect(mockCapNotification.error).not.toHaveBeenCalled();
  });

  it('should handle various image formats', () => {
    const pngContent = '<img src="data:image/png;base64,iVBORw0..." />';
    const jpegContent = '<img src="data:image/jpeg;base64,/9j/4AAQ..." />';
    const gifContent = '<img src="data:image/gif;base64,R0lGODlh..." />';
    const webpContent = '<img src="data:image/webp;base64,UklGRi..." />';
    
    expect(containsBase64Images({content: pngContent, CapNotification: mockCapNotification})).toBe(true);
    expect(containsBase64Images({content: jpegContent, CapNotification: mockCapNotification})).toBe(true);
    expect(containsBase64Images({content: gifContent, CapNotification: mockCapNotification})).toBe(true);
    expect(containsBase64Images({content: webpContent, CapNotification: mockCapNotification})).toBe(true);
    
    expect(mockCapNotification.error).toHaveBeenCalledTimes(4);
  });

  it('should handle mixed content', () => {
    const content = `
      <div>
        <img src="https://example.com/regular-image.jpg" alt="regular" />
        <img src="data:image/png;base64,iVBORw0..." alt="base64" />
        <img src="/local/path/image.gif" alt="local" />
      </div>
    `;
    expect(containsBase64Images({content, CapNotification: mockCapNotification})).toBe(true);
    expect(mockCapNotification.error).toHaveBeenCalled();
  });

  it('should work without CapNotification parameter', () => {
    const content = '<img src="data:image/png;base64,iVBORw0..." alt="test" />';
    expect(containsBase64Images({content})).toBe(true);
    // Should not throw error when CapNotification is undefined
  });

  it('should work without callback parameter', () => {
    const content = '<img src="data:image/png;base64,iVBORw0..." alt="test" />';
    expect(containsBase64Images({content, CapNotification: mockCapNotification})).toBe(true);
    expect(mockCapNotification.error).toHaveBeenCalled();
    // Should not throw error when callback is undefined
  });

  it('should execute callback only when base64 images are found', () => {
    const contentWithBase64 = '<img src="data:image/png;base64,iVBORw0..." alt="test" />';
    const contentWithoutBase64 = '<img src="https://example.com/image.jpg" alt="test" />';
    
    containsBase64Images({content: contentWithBase64, callback: mockCallback});
    expect(mockCallback).toHaveBeenCalledTimes(1);
    
    containsBase64Images({content: contentWithoutBase64, callback: mockCallback});
    expect(mockCallback).toHaveBeenCalledTimes(1); // Should still be 1, not called again
  });
});
