/**
 * Tests for utils/helpers.ts
 */
import {
  storage,
  setCookie,
  getCookie,
  removeCookie,
  getToken,
  setToken,
  removeToken,
  formatDate,
  formatDateTime,
  formatCurrency,
  formatPercentage,
  truncate,
  capitalize,
  isValidEmail,
  isValidPhone,
  cn,
  delay,
  deepClone,
  generateId,
  debounce,
  throttle,
} from '../../utils/helpers';

// Mock localStorage
const localStorageMock = (() => {
  let store: Record<string, string> = {};
  return {
    getItem: jest.fn((key: string) => store[key] || null),
    setItem: jest.fn((key: string, value: string) => {
      store[key] = value;
    }),
    removeItem: jest.fn((key: string) => {
      delete store[key];
    }),
    clear: jest.fn(() => {
      store = {};
    }),
  };
})();

Object.defineProperty(window, 'localStorage', { value: localStorageMock });

// Mock document.cookie
let cookieStore: Record<string, string> = {};
Object.defineProperty(document, 'cookie', {
  get: () => Object.entries(cookieStore).map(([k, v]) => `${k}=${v}`).join('; '),
  set: (value: string) => {
    const [cookiePart] = value.split(';');
    const [key, val] = cookiePart.split('=');
    if (val === '' || value.includes('expires=Thu, 01 Jan 1970')) {
      delete cookieStore[key];
    } else {
      cookieStore[key] = val;
    }
  },
});

describe('Storage Helpers', () => {
  beforeEach(() => {
    localStorageMock.clear();
    jest.clearAllMocks();
  });

  describe('storage.get', () => {
    it('should get item from localStorage', () => {
      localStorageMock.getItem.mockReturnValueOnce('test-value');
      expect(storage.get('test-key')).toBe('test-value');
    });

    it('should return null for non-existent key', () => {
      expect(storage.get('non-existent')).toBeNull();
    });

    it('should return null and log error when localStorage throws', () => {
      const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
      localStorageMock.getItem.mockImplementationOnce(() => {
        throw new Error('Storage error');
      });
      expect(storage.get('test-key')).toBeNull();
      expect(consoleSpy).toHaveBeenCalled();
      consoleSpy.mockRestore();
    });
  });

  describe('storage.set', () => {
    it('should set item in localStorage', () => {
      storage.set('test-key', 'test-value');
      expect(localStorageMock.setItem).toHaveBeenCalledWith('test-key', 'test-value');
    });

    it('should log error when localStorage throws', () => {
      const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
      localStorageMock.setItem.mockImplementationOnce(() => {
        throw new Error('Storage error');
      });
      storage.set('test-key', 'test-value');
      expect(consoleSpy).toHaveBeenCalled();
      consoleSpy.mockRestore();
    });
  });

  describe('storage.remove', () => {
    it('should remove item from localStorage', () => {
      storage.remove('test-key');
      expect(localStorageMock.removeItem).toHaveBeenCalledWith('test-key');
    });

    it('should log error when localStorage throws', () => {
      const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
      localStorageMock.removeItem.mockImplementationOnce(() => {
        throw new Error('Storage error');
      });
      storage.remove('test-key');
      expect(consoleSpy).toHaveBeenCalled();
      consoleSpy.mockRestore();
    });
  });

  describe('storage.clear', () => {
    it('should clear localStorage', () => {
      storage.clear();
      expect(localStorageMock.clear).toHaveBeenCalled();
    });

    it('should log error when localStorage throws', () => {
      const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
      localStorageMock.clear.mockImplementationOnce(() => {
        throw new Error('Storage error');
      });
      storage.clear();
      expect(consoleSpy).toHaveBeenCalled();
      consoleSpy.mockRestore();
    });
  });
});

describe('Cookie Helpers', () => {
  beforeEach(() => {
    cookieStore = {};
  });

  describe('setCookie', () => {
    it('should set a cookie', () => {
      setCookie('test-cookie', 'test-value', 7);
      expect(cookieStore['test-cookie']).toBe('test-value');
    });
  });

  describe('getCookie', () => {
    it('should get a cookie value', () => {
      cookieStore['test-cookie'] = 'test-value';
      expect(getCookie('test-cookie')).toBe('test-value');
    });

    it('should return null for non-existent cookie', () => {
      expect(getCookie('non-existent')).toBeNull();
    });
  });

  describe('removeCookie', () => {
    it('should remove a cookie', () => {
      cookieStore['test-cookie'] = 'test-value';
      removeCookie('test-cookie');
      expect(cookieStore['test-cookie']).toBeUndefined();
    });
  });
});

describe('Token Helpers', () => {
  beforeEach(() => {
    localStorageMock.clear();
    cookieStore = {};
    jest.clearAllMocks();
  });

  describe('getToken', () => {
    it('should get token from localStorage', () => {
      localStorageMock.getItem.mockReturnValueOnce('stored-token');
      expect(getToken()).toBe('stored-token');
    });

    it('should fall back to cookie if localStorage is empty', () => {
      localStorageMock.getItem.mockReturnValueOnce(null);
      cookieStore['auth_token'] = 'cookie-token';
      expect(getToken()).toBe('cookie-token');
    });

    it('should return null if no token exists', () => {
      localStorageMock.getItem.mockReturnValueOnce(null);
      expect(getToken()).toBeNull();
    });
  });

  describe('setToken', () => {
    it('should set token in localStorage and cookie', () => {
      setToken('my-token');
      expect(localStorageMock.setItem).toHaveBeenCalled();
      expect(cookieStore['auth_token']).toBe('my-token');
    });
  });

  describe('removeToken', () => {
    it('should remove token from localStorage and cookie', () => {
      cookieStore['auth_token'] = 'my-token';
      removeToken();
      expect(localStorageMock.removeItem).toHaveBeenCalled();
      expect(cookieStore['auth_token']).toBeUndefined();
    });
  });
});

describe('Date Formatting', () => {
  describe('formatDate', () => {
    it('should format a valid date', () => {
      const result = formatDate('2025-01-15');
      expect(result).toContain('2025');
    });

    it('should return dash for empty date', () => {
      expect(formatDate('')).toBe('-');
    });

    it('should return dash for invalid date', () => {
      expect(formatDate('invalid')).toBe('-');
    });

    it('should format Date object', () => {
      const result = formatDate(new Date('2025-01-15'));
      expect(result).toContain('2025');
    });
  });

  describe('formatDateTime', () => {
    it('should format a valid datetime', () => {
      const result = formatDateTime('2025-01-15T10:30:00');
      expect(result).toContain('2025');
    });

    it('should return dash for empty date', () => {
      expect(formatDateTime('')).toBe('-');
    });

    it('should return dash for invalid date', () => {
      expect(formatDateTime('invalid')).toBe('-');
    });
  });
});

describe('Number Formatting', () => {
  describe('formatCurrency', () => {
    it('should format currency in EUR', () => {
      const result = formatCurrency(100);
      expect(result).toContain('100');
    });

    it('should handle decimals', () => {
      const result = formatCurrency(99.99);
      expect(result).toContain('99');
    });
  });

  describe('formatPercentage', () => {
    it('should format percentage', () => {
      const result = formatPercentage(0.75);
      expect(result).toContain('75');
    });
  });
});

describe('String Helpers', () => {
  describe('truncate', () => {
    it('should truncate long strings', () => {
      expect(truncate('Hello World', 5)).toBe('Hello...');
    });

    it('should not truncate short strings', () => {
      expect(truncate('Hi', 10)).toBe('Hi');
    });

    it('should handle exact length', () => {
      expect(truncate('Hello', 5)).toBe('Hello');
    });
  });

  describe('capitalize', () => {
    it('should capitalize first letter', () => {
      expect(capitalize('hello')).toBe('Hello');
    });

    it('should lowercase the rest', () => {
      expect(capitalize('HELLO')).toBe('Hello');
    });

    it('should handle single character', () => {
      expect(capitalize('h')).toBe('H');
    });
  });
});

describe('Validation Helpers', () => {
  describe('isValidEmail', () => {
    it('should return true for valid emails', () => {
      expect(isValidEmail('test@example.com')).toBe(true);
      expect(isValidEmail('user.name@domain.fr')).toBe(true);
    });

    it('should return false for invalid emails', () => {
      expect(isValidEmail('invalid')).toBe(false);
      expect(isValidEmail('')).toBe(false);
    });

    it('should return false for emails exceeding 254 characters', () => {
      const longEmail = 'a'.repeat(250) + '@test.com';
      expect(isValidEmail(longEmail)).toBe(false);
    });
  });

  describe('isValidPhone', () => {
    it('should return true for valid French phone numbers', () => {
      expect(isValidPhone('0612345678')).toBe(true);
      expect(isValidPhone('+33612345678')).toBe(true);
    });

    it('should return false for invalid phone numbers', () => {
      expect(isValidPhone('123')).toBe(false);
      expect(isValidPhone('')).toBe(false);
    });
  });
});

describe('Class Name Helper', () => {
  describe('cn', () => {
    it('should join class names', () => {
      expect(cn('class1', 'class2')).toBe('class1 class2');
    });

    it('should filter out falsy values', () => {
      expect(cn('class1', false, 'class2', null, undefined)).toBe('class1 class2');
    });

    it('should handle empty input', () => {
      expect(cn()).toBe('');
    });
  });
});

describe('Async Helpers', () => {
  describe('delay', () => {
    it('should resolve after specified time', async () => {
      const start = Date.now();
      await delay(50);
      const elapsed = Date.now() - start;
      expect(elapsed).toBeGreaterThanOrEqual(40);
    });
  });
});

describe('Object Helpers', () => {
  describe('deepClone', () => {
    it('should create a deep copy of an object', () => {
      const original = { a: 1, b: { c: 2 } };
      const cloned = deepClone(original);
      expect(cloned).toEqual(original);
      expect(cloned).not.toBe(original);
      expect(cloned.b).not.toBe(original.b);
    });

    it('should clone arrays', () => {
      const original = [1, 2, { a: 3 }];
      const cloned = deepClone(original);
      expect(cloned).toEqual(original);
      expect(cloned).not.toBe(original);
    });
  });
});

describe('ID Generation', () => {
  describe('generateId', () => {
    it('should generate unique IDs', () => {
      const id1 = generateId();
      const id2 = generateId();
      expect(id1).not.toBe(id2);
    });

    it('should generate non-empty string', () => {
      const id = generateId();
      expect(typeof id).toBe('string');
      expect(id.length).toBeGreaterThan(0);
    });

    it('should use window.crypto when available', () => {
      const id = generateId();
      expect(id).toBeDefined();
      expect(id.length).toBeGreaterThan(5);
    });

    it('should fallback to globalThis.crypto when window.crypto is unavailable', () => {
      const originalWindow = global.window;
      // @ts-ignore
      delete global.window;

      const id = generateId();
      expect(id).toBeDefined();
      expect(typeof id).toBe('string');

      global.window = originalWindow;
    });

    it('should fallback to timestamp when no crypto is available', () => {
      const originalWindow = global.window;
      const originalGlobalThis = global.globalThis;
      const originalCrypto = globalThis.crypto;

      // @ts-ignore
      delete global.window;
      // @ts-ignore
      delete globalThis.crypto;

      const id = generateId();
      expect(id).toBeDefined();
      expect(typeof id).toBe('string');
      expect(id.length).toBeGreaterThan(0);

      global.window = originalWindow;
      globalThis.crypto = originalCrypto;
    });
  });
});

describe('Function Utilities', () => {
  beforeEach(() => {
    jest.useFakeTimers();
  });

  afterEach(() => {
    jest.useRealTimers();
  });

  describe('debounce', () => {
    it('should debounce function calls', () => {
      const fn = jest.fn();
      const debouncedFn = debounce(fn, 100);

      debouncedFn();
      debouncedFn();
      debouncedFn();

      expect(fn).not.toHaveBeenCalled();

      jest.advanceTimersByTime(100);

      expect(fn).toHaveBeenCalledTimes(1);
    });
  });

  describe('throttle', () => {
    it('should throttle function calls', () => {
      const fn = jest.fn();
      const throttledFn = throttle(fn, 100);

      throttledFn();
      throttledFn();
      throttledFn();

      expect(fn).toHaveBeenCalledTimes(1);

      jest.advanceTimersByTime(100);
      throttledFn();

      expect(fn).toHaveBeenCalledTimes(2);
    });
  });
});
