import { isValidHttpUrl, validateUrl } from './urlValidation';

describe('urlValidation', () => {
  describe('isValidHttpUrl', () => {
    describe('Empty and falsy values', () => {
      it('should return false for null', () => {
        expect(isValidHttpUrl(null)).toBe(false);
      });

      it('should return false for undefined', () => {
        expect(isValidHttpUrl(undefined)).toBe(false);
      });

      it('should return false for empty string', () => {
        expect(isValidHttpUrl('')).toBe(false);
      });

      it('should return false for whitespace-only string', () => {
        expect(isValidHttpUrl('   ')).toBe(false);
      });

      it('should return false for tab-only string', () => {
        expect(isValidHttpUrl('\t\t')).toBe(false);
      });

      it('should return false for newline-only string', () => {
        expect(isValidHttpUrl('\n\n')).toBe(false);
      });
    });

    describe('Invalid protocol', () => {
      it('should return false for ftp:// URLs', () => {
        expect(isValidHttpUrl('ftp://example.com')).toBe(false);
      });

      it('should return false for file:// URLs', () => {
        expect(isValidHttpUrl('file:///path/to/file')).toBe(false);
      });

      it('should return false for mailto: URLs', () => {
        expect(isValidHttpUrl('mailto:test@example.com')).toBe(false);
      });

      it('should return false for tel: URLs', () => {
        expect(isValidHttpUrl('tel:+1234567890')).toBe(false);
      });

      it('should return false for javascript: URLs', () => {
        expect(isValidHttpUrl('javascript:alert(1)')).toBe(false);
      });

      it('should return false for URLs without protocol', () => {
        expect(isValidHttpUrl('example.com')).toBe(false);
      });

      it('should return false for URLs with only http (no colon)', () => {
        expect(isValidHttpUrl('http//example.com')).toBe(false);
      });
    });

    describe('Missing TLD (Top Level Domain)', () => {
      it('should return false for URLs without TLD', () => {
        expect(isValidHttpUrl('https://google')).toBe(false);
      });

      it('should return false for URLs with single character after dot', () => {
        expect(isValidHttpUrl('https://example.c')).toBe(false);
      });

      it('should return false for URLs with invalid domain format', () => {
        expect(isValidHttpUrl('https://example')).toBe(false);
      });
    });

    describe('Valid HTTP URLs', () => {
      it('should return true for simple http:// URL', () => {
        expect(isValidHttpUrl('http://example.com')).toBe(true);
      });

      it('should return true for simple https:// URL', () => {
        expect(isValidHttpUrl('https://example.com')).toBe(true);
      });

      it('should return true for URL with path', () => {
        expect(isValidHttpUrl('https://example.com/path')).toBe(true);
      });

      it('should return true for URL with multiple path segments', () => {
        expect(isValidHttpUrl('https://example.com/path/to/resource')).toBe(true);
      });

      it('should return true for URL with query parameters', () => {
        expect(isValidHttpUrl('https://example.com?param=value')).toBe(true);
      });

      it('should return true for URL with multiple query parameters', () => {
        expect(isValidHttpUrl('https://example.com?param1=value1&param2=value2')).toBe(true);
      });

      it('should return true for URL with hash fragment', () => {
        expect(isValidHttpUrl('https://example.com#section')).toBe(true);
      });

      it('should return true for URL with path, query, and hash', () => {
        expect(isValidHttpUrl('https://example.com/path?param=value#section')).toBe(true);
      });

      it('should return true for URL with port number', () => {
        expect(isValidHttpUrl('https://example.com:8080')).toBe(true);
      });

      it('should return true for URL with port and path', () => {
        expect(isValidHttpUrl('https://example.com:8080/path')).toBe(true);
      });

      it('should return true for URL with subdomain', () => {
        expect(isValidHttpUrl('https://subdomain.example.com')).toBe(true);
      });

      it('should return true for URL with multiple subdomains', () => {
        expect(isValidHttpUrl('https://www.subdomain.example.com')).toBe(true);
      });

      it('should return false for URL with IP address (TLD pattern requires domain format)', () => {
        // IP addresses don't match TLD pattern which requires domain.tld format
        expect(isValidHttpUrl('https://192.168.1.1')).toBe(false);
      });

      it('should return false for URL with IP address and port', () => {
        // IP addresses don't match TLD pattern which requires domain.tld format
        expect(isValidHttpUrl('https://192.168.1.1:8080')).toBe(false);
      });

      it('should return true for URL with TLD longer than 2 characters', () => {
        expect(isValidHttpUrl('https://example.info')).toBe(true);
      });

      it('should return true for URL with country code TLD', () => {
        expect(isValidHttpUrl('https://example.co.uk')).toBe(true);
      });
    });

    describe('URLs with whitespace', () => {
      it('should trim leading whitespace and validate', () => {
        expect(isValidHttpUrl('  https://example.com')).toBe(true);
      });

      it('should trim trailing whitespace and validate', () => {
        expect(isValidHttpUrl('https://example.com  ')).toBe(true);
      });

      it('should trim both leading and trailing whitespace', () => {
        expect(isValidHttpUrl('  https://example.com  ')).toBe(true);
      });
    });

    describe('Case insensitivity', () => {
      it('should accept uppercase HTTP protocol', () => {
        expect(isValidHttpUrl('HTTP://EXAMPLE.COM')).toBe(true);
      });

      it('should accept uppercase HTTPS protocol', () => {
        expect(isValidHttpUrl('HTTPS://EXAMPLE.COM')).toBe(true);
      });

      it('should accept mixed case protocol', () => {
        expect(isValidHttpUrl('Http://Example.Com')).toBe(true);
      });
    });

    describe('Edge cases and special characters', () => {
      it('should handle URLs with encoded characters', () => {
        expect(isValidHttpUrl('https://example.com/path%20with%20spaces')).toBe(true);
      });

      it('should handle URLs with special characters in path', () => {
        expect(isValidHttpUrl('https://example.com/path-with-dashes')).toBe(true);
      });

      it('should handle URLs with underscores in domain', () => {
        expect(isValidHttpUrl('https://example_test.com')).toBe(true);
      });

      it('should handle URLs ending with slash', () => {
        expect(isValidHttpUrl('https://example.com/')).toBe(true);
      });

      it('should handle URLs ending with query', () => {
        expect(isValidHttpUrl('https://example.com?')).toBe(true);
      });

      it('should handle URLs ending with hash', () => {
        expect(isValidHttpUrl('https://example.com#')).toBe(true);
      });
    });

    describe('Environment compatibility', () => {
      let originalURL;
      let originalWindow;
      let originalDocument;

      beforeEach(() => {
        // Store original values
        originalURL = global.URL;
        originalWindow = global.window;
        originalDocument = global.document;
      });

      afterEach(() => {
        // Restore original values
        global.URL = originalURL;
        global.window = originalWindow;
        global.document = originalDocument;
      });

      it('should work when URL constructor is available globally', () => {
        // URL constructor is available in Jest/jsdom environment
        expect(isValidHttpUrl('https://example.com')).toBe(true);
      });

      it('should work when URL constructor is available on window', () => {
        // Simulate browser environment
        delete global.URL;
        global.window = { URL: originalURL };
        expect(isValidHttpUrl('https://example.com')).toBe(true);
      });

      it('should fallback to DOM-based validation when URL constructor throws', () => {
        // Mock URL constructor to throw error
        global.URL = jest.fn(() => {
          throw new Error('Invalid URL');
        });
        // DOM should be available in jsdom
        expect(isValidHttpUrl('https://example.com')).toBe(true);
      });

      it('should work with DOM-based validation when URL constructor is not available', () => {
        // Remove URL constructor
        delete global.URL;
        delete global.window;
        // DOM should still be available in jsdom
        expect(isValidHttpUrl('https://example.com')).toBe(true);
      });

      it('should return false when all validation methods fail', () => {
        // Remove URL constructor and document
        delete global.URL;
        delete global.window;
        global.document = undefined;
        // Should still fail regex/TLD checks for invalid URLs
        expect(isValidHttpUrl('ftp://example.com')).toBe(false);
      });
    });

    describe('Invalid URL formats', () => {
      it('should return false for URLs with invalid characters', () => {
        expect(isValidHttpUrl('https://example .com')).toBe(false);
      });

      it('should return false for URLs with spaces in domain', () => {
        expect(isValidHttpUrl('https://exam ple.com')).toBe(false);
      });

      it('should return false for malformed URLs', () => {
        expect(isValidHttpUrl('https://')).toBe(false);
      });

      it('should return false for URLs with only protocol', () => {
        expect(isValidHttpUrl('https:///')).toBe(false);
      });

      it('should return false for URLs with invalid port format', () => {
        expect(isValidHttpUrl('https://example.com:abc')).toBe(false);
      });
    });
  });

  describe('validateUrl', () => {
    let mockFormatMessage;
    const mockMessages = {
      urlRequired: {
        id: 'test.urlRequired',
        defaultMessage: 'URL is required',
      },
      urlInvalid: {
        id: 'test.urlInvalid',
        defaultMessage: 'Please enter a valid URL',
      },
    };

    beforeEach(() => {
      mockFormatMessage = jest.fn((message) => {
        if (message && typeof message === 'object' && message.defaultMessage) {
          return message.defaultMessage;
        }
        return message;
      });
    });

    describe('Empty and falsy values', () => {
      it('should return error message for null', () => {
        const result = validateUrl(null, mockFormatMessage, mockMessages);
        expect(result).toBe('URL is required');
        expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.urlRequired);
        expect(mockFormatMessage).toHaveBeenCalledTimes(1);
      });

      it('should return error message for undefined', () => {
        const result = validateUrl(undefined, mockFormatMessage, mockMessages);
        expect(result).toBe('URL is required');
        expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.urlRequired);
      });

      it('should return error message for empty string', () => {
        const result = validateUrl('', mockFormatMessage, mockMessages);
        expect(result).toBe('URL is required');
        expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.urlRequired);
      });

      it('should return error message for whitespace-only string', () => {
        const result = validateUrl('   ', mockFormatMessage, mockMessages);
        expect(result).toBe('URL is required');
        expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.urlRequired);
      });

      it('should return error message for tab-only string', () => {
        const result = validateUrl('\t\t', mockFormatMessage, mockMessages);
        expect(result).toBe('URL is required');
        expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.urlRequired);
      });
    });

    describe('Invalid URLs', () => {
      it('should return error message for invalid protocol', () => {
        const result = validateUrl('ftp://example.com', mockFormatMessage, mockMessages);
        expect(result).toBe('Please enter a valid URL');
        expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.urlInvalid);
        expect(mockFormatMessage).toHaveBeenCalledTimes(1);
      });

      it('should return error message for URL without TLD', () => {
        const result = validateUrl('https://google', mockFormatMessage, mockMessages);
        expect(result).toBe('Please enter a valid URL');
        expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.urlInvalid);
      });

      it('should return error message for URL without protocol', () => {
        const result = validateUrl('example.com', mockFormatMessage, mockMessages);
        expect(result).toBe('Please enter a valid URL');
        expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.urlInvalid);
      });

      it('should return error message for malformed URL', () => {
        const result = validateUrl('https://', mockFormatMessage, mockMessages);
        expect(result).toBe('Please enter a valid URL');
        expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.urlInvalid);
      });
    });

    describe('Valid URLs', () => {
      it('should return empty string for valid http:// URL', () => {
        const result = validateUrl('http://example.com', mockFormatMessage, mockMessages);
        expect(result).toBe('');
        expect(mockFormatMessage).not.toHaveBeenCalled();
      });

      it('should return empty string for valid https:// URL', () => {
        const result = validateUrl('https://example.com', mockFormatMessage, mockMessages);
        expect(result).toBe('');
        expect(mockFormatMessage).not.toHaveBeenCalled();
      });

      it('should return empty string for URL with path', () => {
        const result = validateUrl('https://example.com/path', mockFormatMessage, mockMessages);
        expect(result).toBe('');
        expect(mockFormatMessage).not.toHaveBeenCalled();
      });

      it('should return empty string for URL with query parameters', () => {
        const result = validateUrl('https://example.com?param=value', mockFormatMessage, mockMessages);
        expect(result).toBe('');
        expect(mockFormatMessage).not.toHaveBeenCalled();
      });

      it('should return empty string for URL with hash', () => {
        const result = validateUrl('https://example.com#section', mockFormatMessage, mockMessages);
        expect(result).toBe('');
        expect(mockFormatMessage).not.toHaveBeenCalled();
      });

      it('should return empty string for URL with port', () => {
        const result = validateUrl('https://example.com:8080', mockFormatMessage, mockMessages);
        expect(result).toBe('');
        expect(mockFormatMessage).not.toHaveBeenCalled();
      });

      it('should return empty string for URL with whitespace that trims to valid URL', () => {
        const result = validateUrl('  https://example.com  ', mockFormatMessage, mockMessages);
        expect(result).toBe('');
        expect(mockFormatMessage).not.toHaveBeenCalled();
      });
    });

    describe('formatMessage function behavior', () => {
      it('should call formatMessage with urlRequired for empty value', () => {
        const customFormatMessage = jest.fn((msg) => `Custom: ${msg.defaultMessage}`);
        validateUrl('', customFormatMessage, mockMessages);
        expect(customFormatMessage).toHaveBeenCalledWith(mockMessages.urlRequired);
        expect(customFormatMessage).not.toHaveBeenCalledWith(mockMessages.urlInvalid);
      });

      it('should call formatMessage with urlInvalid for invalid URL', () => {
        const customFormatMessage = jest.fn((msg) => `Custom: ${msg.defaultMessage}`);
        validateUrl('invalid-url', customFormatMessage, mockMessages);
        expect(customFormatMessage).toHaveBeenCalledWith(mockMessages.urlInvalid);
        expect(customFormatMessage).not.toHaveBeenCalledWith(mockMessages.urlRequired);
      });

      it('should handle formatMessage that returns message object directly', () => {
        const formatMessageReturningObject = jest.fn((msg) => msg);
        const result = validateUrl('', formatMessageReturningObject, mockMessages);
        expect(result).toEqual(mockMessages.urlRequired);
      });

      it('should handle formatMessage that returns string', () => {
        const formatMessageReturningString = jest.fn((msg) => msg.defaultMessage);
        const result = validateUrl('', formatMessageReturningString, mockMessages);
        expect(result).toBe('URL is required');
      });
    });

    describe('Message object variations', () => {
      it('should work with messages that have only defaultMessage', () => {
        const simpleMessages = {
          urlRequired: { defaultMessage: 'Required' },
          urlInvalid: { defaultMessage: 'Invalid' },
        };
        const result = validateUrl('', mockFormatMessage, simpleMessages);
        expect(mockFormatMessage).toHaveBeenCalledWith(simpleMessages.urlRequired);
      });

      it('should work with messages that have id and defaultMessage', () => {
        const result = validateUrl('', mockFormatMessage, mockMessages);
        expect(mockFormatMessage).toHaveBeenCalledWith(mockMessages.urlRequired);
      });
    });
  });
});

