import { DyFM_Crypto } from './crypto.util';

describe('| DyFM_Crypto edge cases', () => {
  const testKey = 'test-secret-key-123';
  const testData = { id: 1, name: 'test' };

  describe('| edge cases', () => {
    describe('| empty and null values', () => {
      it('| should handle empty objects', () => {
        const emptyObj = {};
        const encrypted = DyFM_Crypto.encrypt(emptyObj, testKey);
        const decrypted = DyFM_Crypto.decrypt(encrypted, testKey);
        expect(decrypted).toEqual(emptyObj);
      });

      it('| should handle empty arrays', () => {
        const emptyArr: any[] = [];
        const encrypted = DyFM_Crypto.encrypt(emptyArr, testKey);
        const decrypted = DyFM_Crypto.decrypt(encrypted, testKey);
        expect(decrypted).toEqual(emptyArr);
      });

      it('| should handle null values in objects', () => {
        const dataWithNull = {
          id: 1,
          name: null,
          nested: { value: null }
        };
        const encrypted = DyFM_Crypto.encrypt(dataWithNull, testKey);
        const decrypted = DyFM_Crypto.decrypt(encrypted, testKey);
        expect(decrypted).toEqual(dataWithNull);
      });

      it('| should handle undefined values in objects', () => {
        const dataWithUndefined = {
          id: 1,
          name: undefined,
          nested: { value: undefined }
        };
        const encrypted = DyFM_Crypto.encrypt(dataWithUndefined, testKey);
        const decrypted = DyFM_Crypto.decrypt(encrypted, testKey);
        expect(decrypted).toEqual({ id: 1, nested: {} });
      });
    });

    describe('| key edge cases', () => {
      it('| should handle very long keys', () => {
        const longKey = 'a'.repeat(1000);
        const encrypted = DyFM_Crypto.encrypt(testData, longKey);
        const decrypted = DyFM_Crypto.decrypt(encrypted, longKey);
        expect(decrypted).toEqual(testData);
      });

      it('| should handle empty key', () => {
        expect(() => DyFM_Crypto.encrypt(testData, '')).toThrow();
      });

      it('| should handle whitespace-only keys', () => {
        expect(() => DyFM_Crypto.encrypt(testData, '   ')).toThrow();
      });
    });

    describe('| encrypted data manipulation', () => {
      it('| should detect when encrypted data is truncated', () => {
        const encrypted = DyFM_Crypto.encrypt(testData, testKey);
        const truncated = encrypted.slice(0, -10);
        expect(() => DyFM_Crypto.decrypt(truncated, testKey)).toThrow();
      });

      it('| should detect when encrypted data is extended', () => {
        const encrypted = DyFM_Crypto.encrypt(testData, testKey);
        const extended = encrypted + 'A'.repeat(10);
        expect(() => DyFM_Crypto.decrypt(extended, testKey)).toThrow();
      });

      it('| should detect when encrypted data is modified in the middle', () => {
        const encrypted = DyFM_Crypto.encrypt(testData, testKey);
        const midPoint = Math.floor(encrypted.length / 2);
        const modified = encrypted.slice(0, midPoint) + 'X' + encrypted.slice(midPoint + 1);
        expect(() => DyFM_Crypto.decrypt(modified, testKey)).toThrow();
      });
    });

    describe('| JSON handling edge cases', () => {
      it('| should handle double-stringified JSON data correctly', () => {
        const testData = {
          username: 'Itharen',
          password: 'asdasd',
          roles: [],
          permissions: [],
          activeSubscriptions: [],
          clientUserIds: {},
          oauth2Data: {}
        };

        // Simulate double stringification
        const doubleStringified = JSON.stringify(JSON.stringify(testData));
        
        const encrypted = DyFM_Crypto.encrypt(doubleStringified, testKey);
        const decrypted = DyFM_Crypto.decrypt(encrypted, testKey);

        // The crypto utility should automatically handle double-stringified JSON
        // and return the original object directly
        expect(decrypted).toEqual(testData);
      });

      xit('| should handle character-by-character JSON data correctly', () => {
        // Create a character-by-character object like in the example
        const charByCharData = {
          '0': '{',
          '1': '"',
          '2': '_',
          '3': '_',
          '4': 'c',
          '5': 'r',
          '6': 'e',
          '7': 'a',
          '8': 't',
          '9': 'e',
          '10': 'd',
          '11': '"',
          '12': ':',
          '13': '"',
          '14': '2',
          '15': '0',
          '16': '2',
          '17': '5',
          '18': '-',
          '19': '0',
          '20': '5',
          '21': '-',
          '22': '1',
          '23': '3',
          '24': 'T',
          '25': '1',
          '26': '0',
          '27': ':',
          '28': '4',
          '29': '0',
          '30': ':',
          '31': '4',
          '32': '4',
          '33': '.',
          '34': '7',
          '35': '6',
          '36': '0',
          '37': 'Z',
          '38': '"',
          '39': ',',
          '40': '"',
          '41': '_',
          '42': '_',
          '43': 'l',
          '44': 'a',
          '45': 's',
          '46': 't',
          '47': 'M',
          '48': 'o',
          '49': 'd',
          '50': 'i',
          '51': 'f',
          '52': 'i',
          '53': 'e',
          '54': 'd',
          '55': '"',
          '56': ':',
          '57': '"',
          '58': '2',
          '59': '0',
          '60': '2',
          '61': '5',
          '62': '-',
          '63': '0',
          '64': '5',
          '65': '-',
          '66': '1',
          '67': '3',
          '68': 'T',
          '69': '1',
          '70': '0',
          '71': ':',
          '72': '4',
          '73': '0',
          '74': ':',
          '75': '4',
          '76': '4',
          '77': '.',
          '78': '7',
          '79': '6',
          '80': '0',
          '81': 'Z',
          '82': '"',
          '83': ',',
          '84': '"',
          '85': '_',
          '86': '_',
          '87': 'v',
          '88': '"',
          '89': ':',
          '90': '0',
          '91': ',',
          '92': '"',
          '93': 'u',
          '94': 's',
          '95': 'e',
          '96': 'r',
          '97': 'n',
          '98': 'a',
          '99': 'm',
          '100': 'e',
          '101': '"',
          '102': ':',
          '103': '"',
          '104': 'I',
          '105': 't',
          '106': 'h',
          '107': 'a',
          '108': 'r',
          '109': 'e',
          '110': 'n',
          '111': '"',
          '112': ',',
          '113': '"',
          '114': 'p',
          '115': 'r',
          '116': 'e',
          '117': 'v',
          '118': 'i',
          '119': 'o',
          '120': 'u',
          '121': 's',
          '122': 'E',
          '123': 'm',
          '124': 'a',
          '125': 'i',
          '126': 'l',
          '127': 's',
          '128': '"',
          '129': ':',
          '130': '[',
          '131': ']',
          '132': ',',
          '133': '"',
          '134': 'p',
          '135': 'a',
          '136': 's',
          '137': 's',
          '138': 'w',
          '139': 'o',
          '140': 'r',
          '141': 'd',
          '142': '"',
          '143': ':',
          '144': '"',
          '145': 'a',
          '146': 's',
          '147': 'd',
          '148': 'a',
          '149': 's',
          '150': 'd',
          '151': '"',
          '152': ',',
          '153': '"',
          '154': 'r',
          '155': 'o',
          '156': 'l',
          '157': 'e',
          '158': 's',
          '159': '"',
          '160': ':',
          '161': '[',
          '162': ']',
          '163': ',',
          '164': '"',
          '165': 'p',
          '166': 'e',
          '167': 'r',
          '168': 'm',
          '169': 'i',
          '170': 's',
          '171': 's',
          '172': 'i',
          '173': 'o',
          '174': 'n',
          '175': 's',
          '176': '"',
          '177': ':',
          '178': '[',
          '179': ']',
          '180': ',',
          '181': '"',
          '182': 'a',
          '183': 'c',
          '184': 't',
          '185': 'i',
          '186': 'v',
          '187': 'e',
          '188': 'S',
          '189': 'u',
          '190': 'b',
          '191': 's',
          '192': 'c',
          '193': 'r',
          '194': 'i',
          '195': 'p',
          '196': 't',
          '197': 'i',
          '198': 'o',
          '199': 'n',
          '200': 's',
          '201': '"',
          '202': ':',
          '203': '[',
          '204': ']',
          '205': ',',
          '206': '"',
          '207': 'c',
          '208': 'l',
          '209': 'i',
          '210': 'e',
          '211': 'n',
          '212': 't',
          '213': 'U',
          '214': 's',
          '215': 'e',
          '216': 'r',
          '217': 'I',
          '218': 'd',
          '219': 's',
          '220': '"',
          '221': ':',
          '222': '{',
          '223': '}',
          '224': ',',
          '225': '"',
          '226': 'o',
          '227': 'a',
          '228': 'u',
          '229': 't',
          '230': 'h',
          '231': '2',
          '232': 'D',
          '233': 'a',
          '234': 't',
          '235': 'a',
          '236': '"',
          '237': ':',
          '238': '{',
          '239': '}',
          '240': '}'
        };

        const encrypted = DyFM_Crypto.encrypt(charByCharData, testKey);
        const decrypted = DyFM_Crypto.decrypt(encrypted, testKey);

        // Should be able to parse the decrypted data back to the original object
        expect(decrypted).toEqual(charByCharData);
      });
    });

    describe('| cross-environment consistency', () => {
      it('| should produce same encrypted output for same input across different environments', () => {
        const testData = {
          id: 1,
          name: 'test',
          timestamp: '2024-03-20T10:00:00Z'
        };
        const key = 'test-secret-key-123';

        // Simulate different environments by creating new instances
        const encrypted1 = DyFM_Crypto.encrypt(testData, key);
        const encrypted2 = DyFM_Crypto.encrypt(testData, key);

        expect(encrypted1).toBe(encrypted2);
        expect(DyFM_Crypto.decrypt(encrypted1, key)).toEqual(testData);
        expect(DyFM_Crypto.decrypt(encrypted2, key)).toEqual(testData);
      });

      it('| should maintain encryption consistency with different data types', () => {
        const testCases = [
          { data: { id: 1 }, key: 'key1' },
          { data: 'string data', key: 'key2' },
          { data: [1, 2, 3], key: 'key3' },
          { data: { nested: { value: true } }, key: 'key4' }
        ];

        testCases.forEach(({ data, key }) => {
          const encrypted1 = DyFM_Crypto.encrypt(data, key);
          const encrypted2 = DyFM_Crypto.encrypt(data, key);
          expect(encrypted1).toBe(encrypted2);
        });
      });
    });

    describe('| database persistence', () => {
      interface UserData {
        id: number;
        name: string;
        lastLogin: string;
        preferences: {
          language: string;
          timezone: string;
        };
      }

      it('| should successfully encrypt and decrypt data after database storage simulation', () => {
        const originalData = {
          id: 1,
          name: 'test user',
          email: 'test@example.com',
          settings: {
            theme: 'dark',
            notifications: true
          }
        };
        const key = 'db-storage-key';

        // Simulate database storage and retrieval
        const encrypted = DyFM_Crypto.encrypt(originalData, key);
        const storedData = encrypted; // Simulating database storage
        const retrievedData = storedData; // Simulating database retrieval
        const decrypted = DyFM_Crypto.decrypt(retrievedData, key);

        expect(decrypted).toEqual(originalData);
      });

      it('| should handle multiple encryption/decryption cycles for database operations', () => {
        const userData: UserData = {
          id: 1,
          name: 'test user',
          lastLogin: '2024-01-01T10:00:00.000Z',
          preferences: {
            language: 'en',
            timezone: 'UTC'
          }
        };
        const key = 'persistent-key';

        // Simulate multiple database operations
        const encrypted1 = DyFM_Crypto.encrypt(userData, key);
        const decrypted1 = DyFM_Crypto.decrypt(encrypted1, key) as UserData;
        
        // Simulate data update with a different timestamp
        const updatedData: UserData = { 
          ...decrypted1, 
          lastLogin: '2024-01-02T15:30:00.000Z'
        };
        const encrypted2 = DyFM_Crypto.encrypt(updatedData, key);
        const decrypted2 = DyFM_Crypto.decrypt(encrypted2, key) as UserData;

        expect(decrypted1).toEqual(userData);
        expect(decrypted2).toEqual(updatedData);
        expect(decrypted2.lastLogin).not.toBe(userData.lastLogin);
      });

      it('| should maintain data integrity through multiple database operations', () => {
        const sensitiveData = {
          id: 1,
          password: 'hashed-password-123',
          securityQuestions: [
            { question: 'What is your pet name?', answer: 'Fluffy' },
            { question: 'Where were you born?', answer: 'New York' }
          ]
        };
        const key = 'sensitive-data-key';

        // Simulate multiple database operations with the same data
        const encrypted1 = DyFM_Crypto.encrypt(sensitiveData, key);
        const decrypted1 = DyFM_Crypto.decrypt(encrypted1, key);
        const encrypted2 = DyFM_Crypto.encrypt(decrypted1, key);
        const decrypted2 = DyFM_Crypto.decrypt(encrypted2, key);

        expect(decrypted1).toEqual(sensitiveData);
        expect(decrypted2).toEqual(sensitiveData);
        expect(encrypted1).toBe(encrypted2); // Different encryption instances
      });
    });

    describe('| special characters', () => {
      it('| should handle Unicode characters', () => {
        const unicodeData = {
          text: 'Hello 世界! 🌍',
          emoji: '👋',
          special: '!!!@@@###$$$%%%^^^&&&***((()))___+++---===[[[]]]{{{}}}|||;;;:::,,,...<<<>>>' +
            'ĐĐĐđđđßßß¤¤¤×××÷÷÷¨¨¨¨~~~ˇˇˇ^^^˘˘˘°°°˛˛˛```˙˙˙´´´˝˝˝˝¸¸¸§§§' +
            '???ééééáááűűűúúúőőőóóóüüüöööíííÉÉÉÁÁÁŰŰŰÚÚÚŐŐŐÓÓÓÜÖÖÖÍÍÍ'
        };
        const encrypted = DyFM_Crypto.encrypt(unicodeData, testKey);
        const decrypted = DyFM_Crypto.decrypt(encrypted, testKey);
        expect(decrypted).toEqual(unicodeData);
      });

      it('| should handle special characters in object keys', () => {
        const specialKeyData = {
          'key-with-spaces': 'value',
          'key.with.dots': 'value',
          'key-with-special-chars!@#': 'value',
          'key-with-unicode-世界': 'value'
        };
        const encrypted = DyFM_Crypto.encrypt(specialKeyData, testKey);
        const decrypted = DyFM_Crypto.decrypt(encrypted, testKey);
        expect(decrypted).toEqual(specialKeyData);
      });

      it('| should handle special characters in nested objects', () => {
        const nestedData = {
          level1: {
            'special-key!@#': {
              'nested-key-世界': 'value with special chars!@#'
            }
          }
        };
        const encrypted = DyFM_Crypto.encrypt(nestedData, testKey);
        const decrypted = DyFM_Crypto.decrypt(encrypted, testKey);
        expect(decrypted).toEqual(nestedData);
      });

      it('| should handle special characters in arrays', () => {
        const arrayData = [
          'Hello 世界!',
          { 'special-key': 'value with !@#' },
          ['nested', 'array', 'with', 'special', 'chars!@#']
        ];
        const encrypted = DyFM_Crypto.encrypt(arrayData, testKey);
        const decrypted = DyFM_Crypto.decrypt(encrypted, testKey);
        expect(decrypted).toEqual(arrayData);
      });
    });

    describe('| base64 padding and parsing edge cases', () => {
      it('| should handle encrypted data missing 1 padding character', () => {
        const encrypted = DyFM_Crypto.encrypt(testData, testKey);
        // Remove last character (which might be padding)
        const withoutPadding = encrypted.slice(0, -1);
        expect(() => DyFM_Crypto.decrypt(withoutPadding, testKey)).toThrow();
      });

      it('| should handle encrypted data missing 2 padding characters', () => {
        const encrypted = DyFM_Crypto.encrypt(testData, testKey);
        // Remove last 2 characters
        const withoutPadding = encrypted.slice(0, -2);
        expect(() => DyFM_Crypto.decrypt(withoutPadding, testKey)).toThrow();
      });

      it('| should handle encrypted data missing 3 padding characters', () => {
        const encrypted = DyFM_Crypto.encrypt(testData, testKey);
        // Remove last 3 characters
        const withoutPadding = encrypted.slice(0, -3);
        expect(() => DyFM_Crypto.decrypt(withoutPadding, testKey)).toThrow();
      });

      it('| should detect base64 strings that are too short to be valid (less than 48 bytes)', () => {
        // Create a minimal base64 string that parses to less than 48 bytes
        // 18 bytes = 24 base64 characters (18 * 4/3 = 24)
        const shortBase64 = 'ABCDEFGHIJKLMNOPQRSTUVW'; // 24 chars = 18 bytes when parsed
        expect(() => DyFM_Crypto.decrypt(shortBase64, testKey)).toThrow();
      });

      it('| should handle base64 strings with incorrect padding', () => {
        const encrypted = DyFM_Crypto.encrypt(testData, testKey);
        // Add incorrect padding characters
        const withIncorrectPadding = encrypted + '====';
        expect(() => DyFM_Crypto.decrypt(withIncorrectPadding, testKey)).toThrow();
      });

      it('| should handle URL-safe base64 strings ending with dash', () => {
        // Create a base64 string ending with '-' (which becomes '+' in regular base64)
        const encrypted = DyFM_Crypto.encrypt(testData, testKey);
        // Replace last character with dash if it's not already
        const withDash = encrypted.slice(0, -1) + '-';
        expect(() => DyFM_Crypto.decrypt(withDash, testKey)).toThrow();
      });

      it('| should handle URL-safe base64 strings ending with underscore', () => {
        // Create a base64 string ending with '_' (which becomes '/' in regular base64)
        const encrypted = DyFM_Crypto.encrypt(testData, testKey);
        // Replace last character with underscore if it's not already
        const withUnderscore = encrypted.slice(0, -1) + '_';
        expect(() => DyFM_Crypto.decrypt(withUnderscore, testKey)).toThrow();
      });

      it('| should handle URL-safe base64 strings with no padding characters', () => {
        const encrypted = DyFM_Crypto.encrypt(testData, testKey);
        // Encrypted data should already have no padding (removed in encrypt method)
        // This test verifies that decryption works without padding
        expect(() => DyFM_Crypto.decrypt(encrypted, testKey)).not.toThrow();
        const decrypted = DyFM_Crypto.decrypt(encrypted, testKey);
        expect(decrypted).toEqual(testData);
      });

      it('| should handle very short base64 strings that parse to exactly 18 bytes', () => {
        // Create a base64 string that parses to exactly 18 bytes
        // 18 bytes = 24 base64 characters
        const exactly18Bytes = 'ABCDEFGHIJKLMNOPQRSTUVWX'; // 24 chars
        expect(() => DyFM_Crypto.decrypt(exactly18Bytes, testKey)).toThrow();
      });

      it('| should handle base64 strings that are valid format but too short after parsing', () => {
        // Create a valid URL-safe base64 string that's too short
        const validButShort = 'ABCDEFGHIJKLMNOPQRSTUVWXY'; // 25 chars, valid format
        expect(() => DyFM_Crypto.decrypt(validButShort, testKey)).toThrow();
      });
    });
  });

}); 